Compare commits

...
Author SHA1 Message Date
ArEnSc b96d0ce412 Fixed seeding weights and also added the required value to find the vae. 2024-02-09 22:04:27 -05:00
ArEnSc c92b04eff6 fixing weights seed 2024-02-09 19:29:30 -05:00
ArEnSc 8cf2a17425 Renamed file for better searching and enabling the weights. 2024-02-09 19:05:02 -05:00
Brandon Thomas 7450dd1525 whoops 2024-02-05 05:00:37 -05:00
Brandon Thomas d8e066dc23 codegen db 2024-02-05 04:50:46 -05:00
Brandon Thomas fdae854e3f build es-update-job in docker 2024-02-05 04:48:51 -05:00
Brandon Thomas 541695f028 Merge pull request #296 from storytold/brandon-es-job
elasticsearch update job
2024-02-05 04:40:36 -05:00
Brandon Thomas 0719a929f8 pause on no updates 2024-02-05 04:37:04 -05:00
Brandon Thomas c0c86c90b8 dev secrets 2024-02-05 04:11:49 -05:00
Brandon Thomas a5c8f5fdc6 cleanup 2024-02-05 04:06:59 -05:00
Brandon Thomas 58210f0c25 job almost done 2024-02-05 04:00:37 -05:00
Brandon Thomas ccf910e456 wip elasticsearch job 2024-02-04 21:57:27 -05:00
Brandon Thomas d9197d2fd9 codegen db 2024-02-04 21:57:10 -05:00
Brandon Thomas 61d32a1790 more idempotency on more endpoints 2024-02-04 21:54:02 -05:00
Brandon Thomas 361b0e0894 generic idempotency token query; add to media file uploads 2024-02-04 21:45:21 -05:00
Brandon Thomas 12070bc399 indices on temporal columns in model_weights 2024-02-04 21:26:15 -05:00
Brandon Thomas a49b8b4b74 logging 2024-02-04 02:58:03 -05:00
Brandon Thomas 2422f6d0aa input.mp4 hardcoded 2024-02-04 02:21:52 -05:00
Brandon Thomas a1fc56ecee prompt argument for comfy 2024-02-04 01:48:05 -05:00
Brandon Thomas 05025d8817 maybe fix pathing 2024-02-04 01:04:43 -05:00
Brandon Thomas bcea80c39b get comfyui media downloads working 2024-02-04 00:43:46 -05:00
Brandon Thomas cf59c8e2fc extra logging 2024-02-03 23:49:51 -05:00
Brandon Thomas 5528993768 config for comfyui workflow 2024-02-03 23:44:21 -05:00
Brandon Thomas da9e03e3ae api for delete 2024-02-03 02:07:44 -05:00
Brandon Thomas 76b8b07d8a 6 sd batch size 2024-02-03 01:25:16 -05:00
Brandon Thomas 14cba8b8ed fix sd inference pathing 2024-02-03 01:15:03 -05:00
Brandon Thomas 55805c2d75 safetensors -> pt x2 2024-02-03 00:42:16 -05:00
ArEnSc ae80de8508 Merge pull request #295 from storytold/feature/comfyui
support docker container & more comfyui dependencies
2024-02-03 00:37:05 -05:00
Brandon Thomas b965bfb602 file_size_bytes needs to be BIGINT 2024-02-03 00:20:08 -05:00
saltacc f08cc3fd4a support docker container & more requirements 2024-02-02 21:06:25 -08:00
Brandon Thomas b7853b8914 debugging, safetensors -> pt 2024-02-02 23:57:39 -05:00
Brandon Thomas 26d5d6d272 sd: don't redirect stdout 2024-02-02 23:37:16 -05:00
Brandon Thomas 4e20f63f87 output stderr on failure 2024-02-02 23:28:43 -05:00
Brandon Thomas c8fa722b3d parameter for predefined SD weights 2024-02-02 23:17:12 -05:00
Brandon Thomas 0540d2caba hack for VAE SD support 2024-02-02 22:58:28 -05:00
Brandon Thomas 449c29e824 process and upload glb instead of gltf+bin 2024-02-01 21:09:21 -05:00
Brandon Thomas bf4bd8f21c re-codegen 2024-02-01 20:37:53 -05:00
Brandon Thomas 59bcf8b040 also upload gltf binary companion file 2024-02-01 20:33:52 -05:00
Brandon Thomas 9785f3292d Add GLB 2024-02-01 20:09:40 -05:00
Brandon Thomas 6f7262d0e8 Wrong index type for job_type. Hopefully nobody ran this. 2024-02-01 19:35:48 -05:00
Brandon Thomas ec0b475457 other job types 2024-02-01 03:25:25 -05:00
Brandon Thomas 47db5d0f3d InferenceJobType 2024-01-31 23:26:02 -05:00
Brandon Thomas e9de700f2c Migration to add job_type - we'll need to start populating this. 2024-01-31 23:10:16 -05:00
ArEnSc f365f1a02f Merge pull request #294 from storytold/feature/comfyui
Feature/comfyui
2024-01-31 21:49:33 -05:00
saltacc bfebf60289 properly handle invalid jsonpaths 2024-01-31 17:38:56 -08:00
saltacc f0f2788e1b make things work with new stuff :/ 2024-01-31 17:17:13 -08:00
saltacc 33b66f388f Merge remote-tracking branch 'origin/master' into feature/comfyui
# Conflicts:
#	crates/cli/dev-database-seed/src/seeding/model_weights.rs
2024-01-31 17:07:25 -08:00
Brandon Thomas 387cb22e65 more takedown requests 2024-01-31 02:37:07 -05:00
Brandon Thomas 7248425f30 takedown requests 2024-01-30 17:37:44 -05:00
saltacc cc0e3a2f01 Merge remote-tracking branch 'origin/master' into feature/comfyui 2024-01-30 11:55:59 -08:00
saltacc aa719ba56f download requirements, fixes 2024-01-30 11:55:30 -08:00
Brandon Thomas 1da489a177 fix fbx2gltf script 2024-01-30 04:02:43 -05:00
Brandon Thomas e777d996bc document endpoint 2024-01-29 22:42:46 -05:00
Brandon Thomas eeb6d486e1 enqueue fbx to gltf 2024-01-29 22:23:18 -05:00
Brandon Thomas 57ca10f23d fix test/lint failures 2024-01-29 21:15:17 -05:00
Brandon Thomas 7b97c3bfd2 gltf upload hack 2024-01-28 07:17:11 -05:00
Brandon Thomas 111383a0ae codegen db 2024-01-28 06:56:05 -05:00
Brandon Thomas 542a7c7116 upload fbx 2024-01-28 06:44:29 -05:00
Brandon Thomas c6dc35eccb cleanup; upload fbx 2024-01-28 06:30:22 -05:00
Brandon Thomas d8fbc09867 fbx2gltf should work - just needs endpoint 2024-01-28 05:29:57 -05:00
Brandon Thomas 2b0ebd5350 env vars for development fbx2gltf 2024-01-28 03:39:45 -05:00
Brandon Thomas 8d22697855 better environment variable errors 2024-01-28 03:36:17 -05:00
Brandon Thomas 6cdbe9ea00 configuration for fbx2gltf 2024-01-28 02:21:44 -05:00
Brandon Thomas 15db937039 tests on command runner 2024-01-28 01:35:24 -05:00
Brandon Thomas a01bde2a4b env var policy 2024-01-28 01:08:01 -05:00
Brandon Thomas 98bf91fa17 Merge pull request #292 from storytold/bt-fbx-to-gltf
fbx to gltf (wip)
2024-01-27 23:54:41 -05:00
Brandon Thomas 6f4fe5c542 create command_adapter WIP 2024-01-27 23:54:09 -05:00
Brandon Thomas ebae1dbc9f fbx to gltf 2024-01-27 20:51:36 -05:00
ArEnSc 39a07b25f5 Merge pull request #291 from storytold/feature/sd_finalstand
Fixed GDrive download check.
2024-01-27 01:38:02 -05:00
Brandon Thomas 8ba103fd2b fix test 2024-01-27 00:07:01 -05:00
Brandon Thomas ef53d96a27 fix tests 2024-01-26 23:58:51 -05:00
Brandon Thomas ebc6834128 fix test lints 2024-01-26 23:51:46 -05:00
ArEnSc ea1090b8ef Merge pull request #290 from storytold/feature/sd_finalstand
Feature/sd finalstand
2024-01-26 04:32:40 -05:00
Brandon Thomas 9c9c2b4fff codegen db, fix compile error 2024-01-25 23:01:53 -05:00
Brandon Thomas 7bea2ed724 Inference job stub for format conversion (fbx2gltf) 2024-01-25 22:48:34 -05:00
Brandon Thomas e7f2aed1ef new inference category for fbx -> gltf 2024-01-25 20:26:46 -05:00
Brandon Thomas 13573437fb de-duplication of query logic 2024-01-25 20:17:49 -05:00
Brandon Thomas 215e958405 improve inference-job selection predicates 2024-01-25 20:12:19 -05:00
saltacc 84a2aa6108 use input file, fix ext and prefix 2024-01-23 12:58:09 -08:00
saltacc aff8d5d310 Merge remote-tracking branch 'origin/master' into feature/comfyui
# Conflicts:
#	crates/lib/api_clients/cloud_storage/src/remote_file_manager/media_descriptor.rs
#	crates/lib/api_clients/cloud_storage/src/remote_file_manager/remote_cloud_bucket_details.rs
2024-01-22 13:31:40 -08:00
saltacc 55367e8723 Resolved merge conflict by keeping my changes in process_job.rs 2024-01-22 10:37:19 -08:00
133 changed files with 4279 additions and 871 deletions
+1
View File
@@ -26,6 +26,7 @@
<w>hparams</w>
<w>huggingface</w>
<w>lipsync</w>
<w>mocap</w>
<w>mochi</w>
<w>nestedness</w>
<w>pathbuf</w>
@@ -288,7 +288,7 @@
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
@@ -298,7 +298,7 @@
"name": "user_deleted_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 63,
"max_size": 19
}
@@ -308,7 +308,7 @@
"name": "mod_deleted_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 63,
"max_size": 19
}
@@ -1,12 +0,0 @@
{
"db_name": "MySQL",
"query": "\nINSERT INTO generic_inference_jobs\nSET\n token = ?,\n uuid_idempotency_token = ?,\n\n inference_category = ?,\n maybe_model_type = ?,\n maybe_model_token = ?,\n\n maybe_input_source_token = ?,\n maybe_input_source_token_type = ?,\n\n maybe_raw_inference_text = ?,\n\n maybe_inference_args = ?,\n\n maybe_creator_user_token = ?,\n maybe_creator_anonymous_visitor_token = ?,\n creator_ip_address = ?,\n\n creator_set_visibility = ?,\n\n priority_level = ?,\n is_keepalive_required = ?,\n max_duration_seconds = ?,\n\n is_debug_request = ?,\n maybe_routing_tag = ?,\n\n status = \"pending\"\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 18
},
"nullable": []
},
"hash": "0d780caac7560f84efb50bd2ed876cbdd728b32e26b73dfbb3009ada12985405"
}
@@ -118,7 +118,7 @@
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
@@ -118,7 +118,7 @@
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
@@ -0,0 +1,47 @@
{
"db_name": "MySQL",
"query": "\n SELECT\n token as `token: tokens::tokens::model_weights::ModelWeightToken`,\n weights_type as `weights_type: enums::by_table::model_weights::weights_types::WeightsType`,\n updated_at\n FROM model_weights\n WHERE\n updated_at > ?\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "token: tokens::tokens::model_weights::ModelWeightToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 1,
"name": "weights_type: enums::by_table::model_weights::weights_types::WeightsType",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 2,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false
]
},
"hash": "5c67010afd0bb8aa3cd8f6c2407c4c314ace016f816eca51991720a276340d41"
}
@@ -228,7 +228,7 @@
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
@@ -238,7 +238,7 @@
"name": "user_deleted_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 63,
"max_size": 19
}
@@ -248,7 +248,7 @@
"name": "mod_deleted_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 63,
"max_size": 19
}
@@ -388,7 +388,7 @@
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
@@ -398,7 +398,7 @@
"name": "user_deleted_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 63,
"max_size": 19
}
@@ -408,7 +408,7 @@
"name": "mod_deleted_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 63,
"max_size": 19
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\n INSERT INTO media_files\n SET\n token = ?,\n media_type = ?,\n\n origin_category = ?, \n origin_product_category = ?, \n maybe_origin_model_type = ?, \n maybe_origin_model_token = ?, \n maybe_origin_filename = ?,\n\n is_batch_generated = ?, \n\n maybe_mime_type = ?,\n file_size_bytes = ?,\n maybe_duration_millis = ?,\n maybe_audio_encoding = ?, \n maybe_video_encoding = ?, \n maybe_frame_width = ?, \n maybe_frame_height = ?, \n checksum_sha2 = ?,\n\n public_bucket_directory_hash = ?, \n maybe_public_bucket_prefix = ?, \n maybe_public_bucket_extension = ?, \n extra_file_modification_info = ?, \n\n maybe_creator_user_token = ?, \n maybe_creator_anonymous_visitor_token = ?, \n\n creator_ip_address = ?, \n creator_set_visibility = ?, \n\n maybe_creator_file_synthetic_id = ?, \n maybe_creator_category_synthetic_id = ?,\n\n maybe_mod_user_token = ?, \n is_generated_on_prem = ?, \n generated_by_worker = ?, \n generated_by_cluster = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 30
},
"nullable": []
},
"hash": "80900178668fb38ae51dfe19401c1be37f4ef97fda0198f83239507b8000d1ae"
}
@@ -0,0 +1,91 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension\n\nFROM media_files AS m\nWHERE\n m.token = ?\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "token: tokens::tokens::media_files::MediaFileToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 1,
"name": "media_type: enums::by_table::media_files::media_file_type::MediaFileType",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 2,
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 3,
"name": "creator_set_visibility: enums::common::visibility::Visibility",
"type_info": {
"type": "String",
"flags": "NOT_NULL | BINARY | ENUM",
"char_set": 224,
"max_size": 28
}
},
{
"ordinal": 4,
"name": "public_bucket_directory_hash",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 5,
"name": "maybe_public_bucket_prefix",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
},
{
"ordinal": 6,
"name": "maybe_public_bucket_extension",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 64
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
true,
false,
false,
true,
true
]
},
"hash": "8ba733c63aff16f144417eaa2004d7de479e59f62688016f0ce7b7c61ec1ec57"
}
@@ -328,7 +328,7 @@
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
@@ -338,7 +338,7 @@
"name": "user_deleted_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 63,
"max_size": 19
}
@@ -348,7 +348,7 @@
"name": "mod_deleted_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 63,
"max_size": 19
}
@@ -328,7 +328,7 @@
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
@@ -338,7 +338,7 @@
"name": "user_deleted_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 63,
"max_size": 19
}
@@ -348,7 +348,7 @@
"name": "mod_deleted_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 63,
"max_size": 19
}
@@ -88,7 +88,7 @@
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
@@ -98,7 +98,7 @@
"name": "user_deleted_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 63,
"max_size": 19
}
@@ -108,7 +108,7 @@
"name": "mod_deleted_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 63,
"max_size": 19
}
@@ -58,7 +58,7 @@
"name": "user_deleted_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 63,
"max_size": 19
}
@@ -68,7 +68,7 @@
"name": "mod_deleted_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 63,
"max_size": 19
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "INSERT INTO idempotency_tokens SET idempotency_value = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "cfeb095db83dfad895835d509818ecccc6cb9da2b32b5155e5ca29d56d3b7fc0"
}
@@ -58,7 +58,7 @@
"name": "user_deleted_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 63,
"max_size": 19
}
@@ -68,7 +68,7 @@
"name": "mod_deleted_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 63,
"max_size": 19
}
@@ -288,7 +288,7 @@
"name": "updated_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP | ON_UPDATE_NOW",
"char_set": 63,
"max_size": 19
}
@@ -298,7 +298,7 @@
"name": "user_deleted_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 63,
"max_size": 19
}
@@ -308,7 +308,7 @@
"name": "mod_deleted_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 63,
"max_size": 19
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nINSERT INTO generic_inference_jobs\nSET\n token = ?,\n uuid_idempotency_token = ?,\n\n job_type = ?,\n\n inference_category = ?,\n maybe_model_type = ?,\n maybe_model_token = ?,\n\n maybe_input_source_token = ?,\n maybe_input_source_token_type = ?,\n\n maybe_raw_inference_text = ?,\n\n maybe_inference_args = ?,\n\n maybe_creator_user_token = ?,\n maybe_creator_anonymous_visitor_token = ?,\n creator_ip_address = ?,\n\n creator_set_visibility = ?,\n\n priority_level = ?,\n is_keepalive_required = ?,\n max_duration_seconds = ?,\n\n is_debug_request = ?,\n maybe_routing_tag = ?,\n\n status = \"pending\"\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 19
},
"nullable": []
},
"hash": "e97a0974ed643b8a20d4d319b4aa93aba979d5f8fc0bdc44b77ef4f945b03b35"
}
Generated
+80 -4
View File
@@ -2292,6 +2292,50 @@ dependencies = [
"my-workspace-hack",
]
[[package]]
name = "es-update-job"
version = "0.0.1"
dependencies = [
"actix_helpers",
"anyhow",
"bootstrap",
"chrono",
"concurrency",
"config",
"container_common",
"dotenv",
"easyenv",
"elasticsearch",
"elasticsearch_schema",
"enums",
"errors",
"filesys",
"hashing",
"hostname",
"jobs_common",
"log",
"media",
"memory_caching",
"mimetypes",
"my-workspace-hack",
"mysql_queries",
"once_cell",
"rand 0.8.5",
"redis_common",
"reusable_types",
"serde",
"serde_derive",
"serde_json",
"server_environment",
"sqlx",
"storyteller_root",
"subprocess",
"subprocess_common",
"testing",
"tokens",
"tokio",
]
[[package]]
name = "etcetera"
version = "0.8.0"
@@ -3099,6 +3143,7 @@ dependencies = [
"r2d2_redis",
"rand 0.8.5",
"redis_common",
"reqwest",
"serde",
"serde_derive",
"serde_json",
@@ -3110,6 +3155,7 @@ dependencies = [
"tokens",
"tokio",
"tts_common",
"walkdir",
]
[[package]]
@@ -3632,7 +3678,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"serial_test",
"serial_test 2.0.0",
"sqlx",
"tokens",
"tokio",
@@ -3905,7 +3951,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"serial_test",
"serial_test 2.0.0",
"sqlx",
"tokens",
"tokio",
@@ -5526,7 +5572,21 @@ dependencies = [
"lazy_static",
"log",
"parking_lot 0.12.1",
"serial_test_derive",
"serial_test_derive 2.0.0",
]
[[package]]
name = "serial_test"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953ad9342b3aaca7cb43c45c097dd008d4907070394bd0751a0aa8817e5a018d"
dependencies = [
"dashmap",
"futures 0.3.29",
"lazy_static",
"log",
"parking_lot 0.12.1",
"serial_test_derive 3.0.0",
]
[[package]]
@@ -5540,6 +5600,17 @@ dependencies = [
"syn 2.0.39",
]
[[package]]
name = "serial_test_derive"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b93fb4adc70021ac1b47f7d45e8cc4169baaa7ea58483bc5b721d19a26202212"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.39",
]
[[package]]
name = "server_environment"
version = "0.0.1"
@@ -6127,7 +6198,7 @@ name = "storyteller_root"
version = "0.0.1"
dependencies = [
"my-workspace-hack",
"serial_test",
"serial_test 2.0.0",
"tempdir",
]
@@ -6210,7 +6281,12 @@ dependencies = [
name = "subprocess_common"
version = "0.0.1"
dependencies = [
"errors",
"filesys",
"log",
"my-workspace-hack",
"serial_test 3.0.0",
"subprocess",
]
[[package]]
+1
View File
@@ -73,6 +73,7 @@ members = [
"crates/service/job/analytics_job",
"crates/service/job/download_job",
"crates/service/job/email_sender_job",
"crates/service/job/es-update-job",
"crates/service/job/inference_job",
"crates/service/job/tts_download_job",
"crates/service/job/tts_inference_job",
+13
View File
@@ -0,0 +1,13 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
update model_weights
set
mod_deleted_at = NOW()
where token IN (
'weight_tnhacmxb9qwqrygc3pxvk3ds6',
'weight_9g1pghkc0xhfy5n9m36b11vds',
'weight_5k0kzkftr4x39xyc9yg5p9c86'
);
+2 -1
View File
@@ -375,7 +375,8 @@ where token IN (
'vcm_a47wtfbsjrfc',
'vcm_kygenhbg5mgj',
'vcm_s8gndv19yt7p',
'vcm_ny9pj57w5eyh'
'vcm_ny9pj57w5eyh',
'vcm_jg4rst3j2d7x'
);
@@ -0,0 +1,21 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- Inventory non-deleted
select
m.id,
m.token,
SUBSTRING(m.title, 1, 35) as title,
u.username,
SUBSTRING(m.original_download_url, 1, 35) as url,
m.updated_at
from model_weights as m
left join users AS u on
u.token = m.creator_user_token
where
m.user_deleted_at IS NULL
and m.mod_deleted_at IS NULL
order by m.title asc;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE generic_inference_jobs DROP COLUMN job_type;
@@ -0,0 +1,10 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE generic_inference_jobs
ADD COLUMN job_type VARCHAR(32) DEFAULT NULL
AFTER uuid_idempotency_token;
ALTER TABLE generic_inference_jobs
ADD INDEX index_job_type (job_type);
@@ -0,0 +1,7 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP INDEX index_updated_at ON model_weights;
DROP INDEX index_user_deleted_at ON model_weights;
DROP INDEX index_mod_deleted_at ON model_weights;
@@ -0,0 +1,7 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE INDEX index_updated_at ON model_weights (updated_at);
CREATE INDEX index_user_deleted_at ON model_weights (user_deleted_at);
CREATE INDEX index_mod_deleted_at ON model_weights (mod_deleted_at);
@@ -17,12 +17,27 @@ CREATE TABLE generic_inference_jobs (
-- ========== INFERENCE DETAILS ==========
-- The new enum for the type of job, which will eventually replace `inference_category` and `maybe_model_type`.
-- This isn't fully supported yet in the inference-job, but we'll start populating it and adding logic around it.
-- We'll start this out as nullable, then migrate the old rows with a default column value.
job_type VARCHAR(32) DEFAULT NULL,
-- NB: This is becoming a problematic field and is becoming conflated with `maybe_model_type`.
-- We're using this to handle job dispatching, but the latter is being used to load container
-- dependencies at startup (even if it isn't a real model type or a polymorphic foreign key).
-- This is pretty gross and horrible.
--
-- Broad category of inference
-- Examples (may not be up to date):
-- * text_to_speech
-- * voice_conversion
inference_category VARCHAR(32) NOT NULL,
-- NB: See notes on inference_category. This is becoming a problematic field. We're using
-- this to load the container spin-up arguments and dependencies, but the former is being
-- used to dispatch the job. `inference_category` is the one that should probably go away,
-- and this column should be renamed to `inference_job_type`.
--
-- Potential part of the composite foreign key to the primary model being used, if any.
-- This will normally live in `maybe_inference_args`, but in this case, it's useful for
-- running easy database analytical queries.
@@ -239,6 +254,7 @@ CREATE TABLE generic_inference_jobs (
PRIMARY KEY (id),
UNIQUE KEY (token),
UNIQUE KEY (uuid_idempotency_token),
KEY index_job_type (job_type),
KEY index_inference_category (inference_category),
KEY index_maybe_model_type_and_maybe_model_token (maybe_model_type, maybe_model_token),
KEY index_maybe_model_type (maybe_model_type),
@@ -95,6 +95,7 @@ CREATE TABLE model_weights (
-- The filename that was used at upload time (if available)
original_filename VARCHAR(255) DEFAULT NULL,
-- TODO: Make this a BIGINT so we can store more than 2GB.
-- File characteristics
file_size_bytes INT(10) NOT NULL DEFAULT 0,
@@ -171,6 +172,9 @@ CREATE TABLE model_weights (
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_cover_media_file_token (maybe_cover_image_media_file_token)
KEY fk_maybe_cover_media_file_token (maybe_cover_image_media_file_token),
KEY index_updated_at (updated_at),
KEY index_user_deleted_at (user_deleted_at),
KEY index_mod_deleted_at (user_deleted_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -73,6 +73,14 @@ impl MediaFileBucketPath {
pub fn get_basename(&self) -> &str {
&self.filename
}
pub fn get_optional_prefix(&self) -> Option<&str> {
self.optional_prefix.as_deref()
}
pub fn get_optional_extension(&self) -> Option<&str> {
self.optional_extension.as_deref()
}
}
#[cfg(test)]
@@ -12,6 +12,7 @@ const ORIGINAL_FILE_BASENAME : &str = "original_upload.bin";
/// The original user upload file.
/// It may have derivative files (down samples, crops, etc.) that live alongside it.
#[derive(Clone)]
#[deprecated(note = "Use media_files records and MediaFileBucketPath instead. media_uploads records are DEPRECATED.")]
pub struct MediaUploadOriginalFilePath {
directory: MediaUploadDirectory,
full_object_path: String,
@@ -21,11 +22,13 @@ impl PublicPath for MediaUploadOriginalFilePath {}
impl MediaUploadOriginalFilePath {
#[deprecated(note = "Use media_files records and MediaFileBucketPath instead. media_uploads records are DEPRECATED.")]
pub fn generate_new() -> Self {
let entropy = crockford_entropy_lower(32);
Self::from_object_hash(&entropy)
}
#[deprecated(note = "Use media_files records and MediaFileBucketPath instead. media_uploads records are DEPRECATED.")]
pub fn from_object_hash(hash: &str) -> Self {
// TODO: Path construction could be cleaner.
let directory = MediaUploadDirectory::from_object_hash(hash);
@@ -36,22 +39,27 @@ impl MediaUploadOriginalFilePath {
}
}
#[deprecated(note = "Use media_files records and MediaFileBucketPath instead. media_uploads records are DEPRECATED.")]
pub fn get_full_object_path_str(&self) -> &str {
&self.full_object_path
}
#[deprecated(note = "Use media_files records and MediaFileBucketPath instead. media_uploads records are DEPRECATED.")]
pub fn to_full_object_pathbuf(&self) -> PathBuf {
PathBuf::from(&self.full_object_path)
}
#[deprecated(note = "Use media_files records and MediaFileBucketPath instead. media_uploads records are DEPRECATED.")]
pub fn get_directory(&self) -> &MediaUploadDirectory {
&self.directory
}
#[deprecated(note = "Use media_files records and MediaFileBucketPath instead. media_uploads records are DEPRECATED.")]
pub fn get_object_hash(&self) -> &str {
&self.directory.get_object_hash()
}
#[deprecated(note = "Use media_files records and MediaFileBucketPath instead. media_uploads records are DEPRECATED.")]
pub fn get_basename() -> &'static str {
ORIGINAL_FILE_BASENAME
}
@@ -18,7 +18,7 @@ use strum::EnumIter;
/// Another benefit is that we'll surface all of the various types of failure and perhaps eventually
/// come to handle some in a cross-cutting way.
///
/// DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY.
/// YOU CAN ADD NEW VALUES, BUT DO NOT CHANGE EXISTING VALUES WITHOUT A MIGRATION STRATEGY.
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
pub enum FrontendFailureCategory {
@@ -11,7 +11,7 @@ use strum::EnumIter;
/// Each "category" of inference is identified by the following enum variants.
/// These types are present in the HTTP API and database columns as serialized here.
///
/// DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY.
/// YOU CAN ADD NEW VALUES, BUT DO NOT CHANGE EXISTING VALUES WITHOUT A MIGRATION STRATEGY.
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize, Default)]
pub enum InferenceCategory {
@@ -37,6 +37,9 @@ pub enum InferenceCategory {
#[serde(rename = "workflow")]
Workflow,
#[serde(rename = "format_conversion")]
FormatConversion,
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
@@ -54,6 +57,7 @@ impl InferenceCategory {
Self::ImageGeneration => "image_generation",
Self::Mocap => "mocap",
Self::Workflow => "workflow",
Self::FormatConversion => "format_conversion",
}
}
@@ -66,6 +70,7 @@ impl InferenceCategory {
"image_generation" => Ok(Self::ImageGeneration),
"mocap" => Ok(Self::Mocap),
"workflow" => Ok(Self::Workflow),
"format_conversion" => Ok(Self::FormatConversion),
_ => Err(format!("invalid value: {:?}", value)),
}
}
@@ -81,6 +86,7 @@ impl InferenceCategory {
Self::ImageGeneration,
Self::Mocap,
Self::Workflow,
Self::FormatConversion,
])
}
}
@@ -90,55 +96,92 @@ mod tests {
use crate::by_table::generic_inference_jobs::inference_category::InferenceCategory;
use crate::test_helpers::assert_serialization;
#[test]
fn test_serialization() {
assert_serialization(InferenceCategory::LipsyncAnimation, "lipsync_animation");
assert_serialization(InferenceCategory::TextToSpeech, "text_to_speech");
assert_serialization(InferenceCategory::VoiceConversion, "voice_conversion");
assert_serialization(InferenceCategory::VideoFilter, "video_filter");
assert_serialization(InferenceCategory::ImageGeneration, "image_generation");
assert_serialization(InferenceCategory::Mocap, "mocap");
assert_serialization(InferenceCategory::Workflow, "workflow")
mod explicit_checks {
use super::*;
#[test]
fn test_serialization() {
assert_serialization(InferenceCategory::LipsyncAnimation, "lipsync_animation");
assert_serialization(InferenceCategory::TextToSpeech, "text_to_speech");
assert_serialization(InferenceCategory::VoiceConversion, "voice_conversion");
assert_serialization(InferenceCategory::VideoFilter, "video_filter");
assert_serialization(InferenceCategory::ImageGeneration, "image_generation");
assert_serialization(InferenceCategory::Mocap, "mocap");
assert_serialization(InferenceCategory::Workflow, "workflow");
assert_serialization(InferenceCategory::FormatConversion, "format_conversion");
}
#[test]
fn to_str() {
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::VideoFilter.to_str(), "video_filter");
assert_eq!(InferenceCategory::ImageGeneration.to_str(), "image_generation");
assert_eq!(InferenceCategory::Mocap.to_str(), "mocap");
assert_eq!(InferenceCategory::Workflow.to_str(), "workflow");
assert_eq!(InferenceCategory::FormatConversion.to_str(), "format_conversion");
}
#[test]
fn from_str() {
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("video_filter").unwrap(), InferenceCategory::VideoFilter);
assert_eq!(InferenceCategory::from_str("image_generation").unwrap(), InferenceCategory::ImageGeneration);
assert_eq!(InferenceCategory::from_str("mocap").unwrap(), InferenceCategory::Mocap);
assert_eq!(InferenceCategory::from_str("workflow").unwrap(), InferenceCategory::Workflow);
assert_eq!(InferenceCategory::from_str("format_conversion").unwrap(), InferenceCategory::FormatConversion);
}
#[test]
fn all_variants() {
// Static check
let mut variants = InferenceCategory::all_variants();
assert_eq!(variants.len(), 8);
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::VideoFilter));
assert_eq!(variants.pop_first(), Some(InferenceCategory::ImageGeneration));
assert_eq!(variants.pop_first(), Some(InferenceCategory::Mocap));
assert_eq!(variants.pop_first(), Some(InferenceCategory::Workflow));
assert_eq!(variants.pop_first(), Some(InferenceCategory::FormatConversion));
assert_eq!(variants.pop_first(), None);
// Generated check
use strum::IntoEnumIterator;
assert_eq!(InferenceCategory::all_variants().len(), InferenceCategory::iter().len());
}
}
#[test]
fn to_str() {
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::VideoFilter.to_str(), "video_filter");
assert_eq!(InferenceCategory::ImageGeneration.to_str(), "image_generation");
assert_eq!(InferenceCategory::Mocap.to_str(), "mocap");
assert_eq!(InferenceCategory::Workflow.to_str(), "workflow");
}
mod mechanical_checks {
use super::*;
#[test]
fn from_str() {
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("video_filter").unwrap(), InferenceCategory::VideoFilter);
assert_eq!(InferenceCategory::from_str("image_generation").unwrap(), InferenceCategory::ImageGeneration);
assert_eq!(InferenceCategory::from_str("mocap").unwrap(), InferenceCategory::Mocap);
assert_eq!(InferenceCategory::from_str("workflow").unwrap(), InferenceCategory::Workflow);
}
#[test]
fn variant_length() {
use strum::IntoEnumIterator;
assert_eq!(InferenceCategory::all_variants().len(), InferenceCategory::iter().len());
}
#[test]
fn all_variants() {
// Static check
let mut variants = InferenceCategory::all_variants();
assert_eq!(variants.len(), 7);
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::VideoFilter));
assert_eq!(variants.pop_first(), Some(InferenceCategory::ImageGeneration));
assert_eq!(variants.pop_first(), Some(InferenceCategory::Mocap));
assert_eq!(variants.pop_first(), Some(InferenceCategory::Workflow));
assert_eq!(variants.pop_first(), None);
#[test]
fn round_trip() {
for variant in InferenceCategory::all_variants() {
assert_eq!(variant, InferenceCategory::from_str(variant.to_str()).unwrap());
assert_eq!(variant, InferenceCategory::from_str(&format!("{}", variant)).unwrap());
assert_eq!(variant, InferenceCategory::from_str(&format!("{:?}", variant)).unwrap());
}
}
// Generated check
use strum::IntoEnumIterator;
assert_eq!(InferenceCategory::all_variants().len(), InferenceCategory::iter().len());
#[test]
fn serialized_length_ok_for_database() {
const MAX_LENGTH : usize = 32;
for variant in InferenceCategory::all_variants() {
let serialized = variant.to_str();
assert!(serialized.len() > 0, "variant {:?} is too short", variant);
assert!(serialized.len() <= MAX_LENGTH, "variant {:?} is too long", variant);
}
}
}
}
@@ -7,7 +7,7 @@ use strum::EnumIter;
/// Used in the `generic_inference_jobs` table in `VARCHAR(32)` field `maybe_input_source_token`.
///
/// DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY.
/// YOU CAN ADD NEW VALUES, BUT DO NOT CHANGE EXISTING VALUES WITHOUT A MIGRATION STRATEGY.
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
pub enum InferenceInputSourceTokenType {
@@ -0,0 +1,227 @@
use std::collections::BTreeSet;
#[cfg(test)]
use strum::EnumCount;
#[cfg(test)]
use strum::EnumIter;
/// Used in the `generic_inference_jobs` table in `VARCHAR(32)` field `job_type`.
///
/// TODO(bt,2024-02-01): This will replace "inference_category" and "maybe_model_type" for job control and dispatch,
/// since those mechanisms are overloaded and inconsistent.
///
/// YOU CAN ADD NEW VALUES, BUT DO NOT CHANGE EXISTING VALUES WITHOUT A MIGRATION STRATEGY.
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum InferenceJobType {
/// Jobs that run ComfyUI workflows
ComfyUi,
/// A job that turns "FBX" game engine files into "GLTF" files (Bevy-compatible).
#[serde(rename = "convert_fbx_gltf")]
ConvertFbxToGltf,
/// Process a video into BVH mocap animation data for game engines
MocapNet,
/// Re-render a video is a video style transfer algorithm. We developed code
/// around it, but chose to develop AnimateDiff / ComfyUI support instead.
RerenderAVideo,
/// RVC is a voice conversion model. RVCv2 is the most popular such model currently.
#[serde(rename = "rvc_v2")]
RvcV2,
/// SadTalker does image-to-video lip-syncing when given an audio file and image.
SadTalker,
/// so-vits-svc voice conversion. This predates RVCv2.
SoVitsSvc,
/// Stable diffusion image generation
StableDiffusion,
/// StyleTTS2 is a zero shot multi-speaker TTS model.
/// This job type should handle both speaker vector encoding and inference.
#[serde(rename = "styletts2")]
StyleTTS2,
/// TT2 Text to speech
Tacotron2,
/// A value we may use in the future for historical jobs
/// (i.e. when we backfill the database column and make it non-nullable)
#[default]
Unknown,
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
impl_enum_display_and_debug_using_to_str!(InferenceJobType);
impl_mysql_enum_coders!(InferenceJobType);
/// NB: Legacy API for older code.
impl InferenceJobType {
pub fn to_str(&self) -> &'static str {
match self {
Self::ComfyUi => "comfy_ui",
Self::ConvertFbxToGltf => "convert_fbx_gltf",
Self::MocapNet => "mocap_net",
Self::RerenderAVideo => "rerender_a_video",
Self::RvcV2 => "rvc_v2",
Self::SadTalker => "sad_talker",
Self::SoVitsSvc => "so_vits_svc",
Self::StableDiffusion => "stable_diffusion",
Self::StyleTTS2 => "styletts2",
Self::Tacotron2 => "tacotron2",
Self::Unknown => "unknown",
}
}
pub fn from_str(value: &str) -> Result<Self, String> {
match value {
"comfy_ui" => Ok(Self::ComfyUi),
"convert_fbx_gltf" => Ok(Self::ConvertFbxToGltf),
"mocap_net" => Ok(Self::MocapNet),
"rerender_a_video" => Ok(Self::RerenderAVideo),
"rvc_v2" => Ok(Self::RvcV2),
"sad_talker" => Ok(Self::SadTalker),
"so_vits_svc" => Ok(Self::SoVitsSvc),
"stable_diffusion" => Ok(Self::StableDiffusion),
"styletts2" => Ok(Self::StyleTTS2),
"tacotron2" => Ok(Self::Tacotron2),
"unknown" => Ok(Self::Unknown),
_ => Err(format!("invalid value: {:?}", value)),
}
}
pub fn all_variants() -> BTreeSet<Self> {
// NB: BTreeSet is sorted
// NB: BTreeSet::from() isn't const, but not worth using LazyStatic, etc.
BTreeSet::from([
Self::ComfyUi,
Self::ConvertFbxToGltf,
Self::MocapNet,
Self::RerenderAVideo,
Self::RvcV2,
Self::SadTalker,
Self::SoVitsSvc,
Self::StableDiffusion,
Self::StyleTTS2,
Self::Tacotron2,
Self::Unknown,
])
}
}
#[cfg(test)]
mod tests {
use crate::by_table::generic_inference_jobs::inference_job_type::InferenceJobType;
use crate::test_helpers::assert_serialization;
mod explicit_checks {
use super::*;
#[test]
fn default() {
assert_eq!(InferenceJobType::default(), InferenceJobType::Unknown);
}
#[test]
fn test_serialization() {
assert_serialization(InferenceJobType::ComfyUi, "comfy_ui");
assert_serialization(InferenceJobType::ConvertFbxToGltf, "convert_fbx_gltf");
assert_serialization(InferenceJobType::MocapNet, "mocap_net");
assert_serialization(InferenceJobType::RerenderAVideo, "rerender_a_video");
assert_serialization(InferenceJobType::RvcV2, "rvc_v2");
assert_serialization(InferenceJobType::SadTalker, "sad_talker");
assert_serialization(InferenceJobType::SoVitsSvc, "so_vits_svc");
assert_serialization(InferenceJobType::StableDiffusion, "stable_diffusion");
assert_serialization(InferenceJobType::StyleTTS2, "styletts2");
assert_serialization(InferenceJobType::Tacotron2, "tacotron2");
assert_serialization(InferenceJobType::Unknown, "unknown");
}
#[test]
fn to_str() {
assert_eq!(InferenceJobType::ComfyUi.to_str(), "comfy_ui");
assert_eq!(InferenceJobType::ConvertFbxToGltf.to_str(), "convert_fbx_gltf");
assert_eq!(InferenceJobType::MocapNet.to_str(), "mocap_net");
assert_eq!(InferenceJobType::RerenderAVideo.to_str(), "rerender_a_video");
assert_eq!(InferenceJobType::RvcV2.to_str(), "rvc_v2");
assert_eq!(InferenceJobType::SadTalker.to_str(), "sad_talker");
assert_eq!(InferenceJobType::SoVitsSvc.to_str(), "so_vits_svc");
assert_eq!(InferenceJobType::StableDiffusion.to_str(), "stable_diffusion");
assert_eq!(InferenceJobType::StyleTTS2.to_str(), "styletts2");
assert_eq!(InferenceJobType::Tacotron2.to_str(), "tacotron2");
assert_eq!(InferenceJobType::Unknown.to_str(), "unknown");
}
#[test]
fn from_str() {
assert_eq!(InferenceJobType::from_str("comfy_ui").unwrap(), InferenceJobType::ComfyUi);
assert_eq!(InferenceJobType::from_str("convert_fbx_gltf").unwrap(), InferenceJobType::ConvertFbxToGltf);
assert_eq!(InferenceJobType::from_str("mocap_net").unwrap(), InferenceJobType::MocapNet);
assert_eq!(InferenceJobType::from_str("rerender_a_video").unwrap(), InferenceJobType::RerenderAVideo);
assert_eq!(InferenceJobType::from_str("rvc_v2").unwrap(), InferenceJobType::RvcV2);
assert_eq!(InferenceJobType::from_str("sad_talker").unwrap(), InferenceJobType::SadTalker);
assert_eq!(InferenceJobType::from_str("so_vits_svc").unwrap(), InferenceJobType::SoVitsSvc);
assert_eq!(InferenceJobType::from_str("stable_diffusion").unwrap(), InferenceJobType::StableDiffusion);
assert_eq!(InferenceJobType::from_str("styletts2").unwrap(), InferenceJobType::StyleTTS2);
assert_eq!(InferenceJobType::from_str("tacotron2").unwrap(), InferenceJobType::Tacotron2);
assert_eq!(InferenceJobType::from_str("unknown").unwrap(), InferenceJobType::Unknown);
}
#[test]
fn all_variants() {
// Static check
let mut variants = InferenceJobType::all_variants();
assert_eq!(variants.len(), 11);
assert_eq!(variants.pop_first(), Some(InferenceJobType::ComfyUi));
assert_eq!(variants.pop_first(), Some(InferenceJobType::ConvertFbxToGltf));
assert_eq!(variants.pop_first(), Some(InferenceJobType::MocapNet));
assert_eq!(variants.pop_first(), Some(InferenceJobType::RerenderAVideo));
assert_eq!(variants.pop_first(), Some(InferenceJobType::RvcV2));
assert_eq!(variants.pop_first(), Some(InferenceJobType::SadTalker));
assert_eq!(variants.pop_first(), Some(InferenceJobType::SoVitsSvc));
assert_eq!(variants.pop_first(), Some(InferenceJobType::StableDiffusion));
assert_eq!(variants.pop_first(), Some(InferenceJobType::StyleTTS2));
assert_eq!(variants.pop_first(), Some(InferenceJobType::Tacotron2));
assert_eq!(variants.pop_first(), Some(InferenceJobType::Unknown));
assert_eq!(variants.pop_first(), None);
// Generated check
use strum::IntoEnumIterator;
assert_eq!(InferenceJobType::all_variants().len(), InferenceJobType::iter().len());
}
}
mod mechanical_checks {
use super::*;
#[test]
fn variant_length() {
use strum::IntoEnumIterator;
assert_eq!(InferenceJobType::all_variants().len(), InferenceJobType::iter().len());
}
#[test]
fn round_trip() {
for variant in InferenceJobType::all_variants() {
assert_eq!(variant, InferenceJobType::from_str(variant.to_str()).unwrap());
assert_eq!(variant, InferenceJobType::from_str(&format!("{}", variant)).unwrap());
assert_eq!(variant, InferenceJobType::from_str(&format!("{:?}", variant)).unwrap());
}
}
#[test]
fn serialized_length_ok_for_database() {
const MAX_LENGTH : usize = 32;
for variant in InferenceJobType::all_variants() {
let serialized = variant.to_str();
assert!(serialized.len() > 0, "variant {:?} is too short", variant);
assert!(serialized.len() <= MAX_LENGTH, "variant {:?} is too long", variant);
}
}
}
}
@@ -13,7 +13,7 @@ use strum::EnumIter;
///
/// These types are present in the HTTP API and database columns as serialized here.
///
/// DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY.
/// YOU CAN ADD NEW VALUES, BUT DO NOT CHANGE EXISTING VALUES WITHOUT A MIGRATION STRATEGY.
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
pub enum InferenceModelType {
@@ -41,6 +41,9 @@ pub enum InferenceModelType {
ComfyUi,
#[serde(rename = "styletts2")]
StyleTTS2,
/// A job that turns "FBX" game engine files into "GLTF" files (Bevy-compatible).
#[serde(rename = "convert_fbx_gltf")]
ConvertFbxToGltf,
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
@@ -62,6 +65,7 @@ impl InferenceModelType {
Self::MocapNet => "mocap_net",
Self::StyleTTS2 => "styletts2",
Self::ComfyUi => "comfy_ui",
Self::ConvertFbxToGltf => "convert_fbx_gltf",
}
}
@@ -78,25 +82,27 @@ impl InferenceModelType {
"mocap_net" => Ok(Self::MocapNet),
"styletts2" => Ok(Self::StyleTTS2),
"comfy_ui" => Ok(Self::ComfyUi),
"convert_fbx_gltf" => Ok(Self::ConvertFbxToGltf),
_ => Err(format!("invalid value: {:?}", value)),
}
}
pub fn all_variants() -> BTreeSet<InferenceModelType> {
pub fn all_variants() -> BTreeSet<Self> {
// NB: BTreeSet is sorted
// NB: BTreeSet::from() isn't const, but not worth using LazyStatic, etc.
BTreeSet::from([
InferenceModelType::RvcV2,
InferenceModelType::SadTalker,
InferenceModelType::SoVitsSvc,
InferenceModelType::Tacotron2,
InferenceModelType::Vits,
InferenceModelType::VallEX,
InferenceModelType::RerenderAVideo,
InferenceModelType::StableDiffusion,
InferenceModelType::MocapNet,
InferenceModelType::StyleTTS2,
InferenceModelType::ComfyUi,
Self::RvcV2,
Self::SadTalker,
Self::SoVitsSvc,
Self::Tacotron2,
Self::Vits,
Self::VallEX,
Self::RerenderAVideo,
Self::StableDiffusion,
Self::MocapNet,
Self::StyleTTS2,
Self::ComfyUi,
Self::ConvertFbxToGltf,
])
}
}
@@ -106,71 +112,108 @@ mod tests {
use crate::by_table::generic_inference_jobs::inference_model_type::InferenceModelType;
use crate::test_helpers::assert_serialization;
#[test]
fn test_serialization() {
assert_serialization(InferenceModelType::RvcV2, "rvc_v2");
assert_serialization(InferenceModelType::SadTalker, "sad_talker");
assert_serialization(InferenceModelType::SoVitsSvc, "so_vits_svc");
assert_serialization(InferenceModelType::Tacotron2, "tacotron2");
assert_serialization(InferenceModelType::Vits, "vits");
assert_serialization(InferenceModelType::VallEX, "vall_e_x");
assert_serialization(InferenceModelType::RerenderAVideo, "rerender_a_video");
assert_serialization(InferenceModelType::StableDiffusion, "stable_diffusion");
assert_serialization(InferenceModelType::MocapNet, "mocap_net");
assert_serialization(InferenceModelType::ComfyUi, "comfy_ui");
assert_serialization(InferenceModelType::StyleTTS2, "styletts2");
mod explicit_checks {
use super::*;
#[test]
fn test_serialization() {
assert_serialization(InferenceModelType::RvcV2, "rvc_v2");
assert_serialization(InferenceModelType::SadTalker, "sad_talker");
assert_serialization(InferenceModelType::SoVitsSvc, "so_vits_svc");
assert_serialization(InferenceModelType::Tacotron2, "tacotron2");
assert_serialization(InferenceModelType::Vits, "vits");
assert_serialization(InferenceModelType::VallEX, "vall_e_x");
assert_serialization(InferenceModelType::RerenderAVideo, "rerender_a_video");
assert_serialization(InferenceModelType::StableDiffusion, "stable_diffusion");
assert_serialization(InferenceModelType::MocapNet, "mocap_net");
assert_serialization(InferenceModelType::ComfyUi, "comfy_ui");
assert_serialization(InferenceModelType::StyleTTS2, "styletts2");
assert_serialization(InferenceModelType::ConvertFbxToGltf, "convert_fbx_gltf");
}
#[test]
fn to_str() {
assert_eq!(InferenceModelType::RvcV2.to_str(), "rvc_v2");
assert_eq!(InferenceModelType::SadTalker.to_str(), "sad_talker");
assert_eq!(InferenceModelType::SoVitsSvc.to_str(), "so_vits_svc");
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");
assert_eq!(InferenceModelType::StableDiffusion.to_str(), "stable_diffusion");
assert_eq!(InferenceModelType::MocapNet.to_str(), "mocap_net");
assert_eq!(InferenceModelType::StyleTTS2.to_str(), "styletts2");
assert_eq!(InferenceModelType::ComfyUi.to_str(), "comfy_ui");
assert_eq!(InferenceModelType::ConvertFbxToGltf.to_str(), "convert_fbx_gltf");
}
#[test]
fn from_str() {
assert_eq!(InferenceModelType::from_str("rvc_v2").unwrap(), InferenceModelType::RvcV2);
assert_eq!(InferenceModelType::from_str("sad_talker").unwrap(), InferenceModelType::SadTalker);
assert_eq!(InferenceModelType::from_str("so_vits_svc").unwrap(), InferenceModelType::SoVitsSvc);
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);
assert_eq!(InferenceModelType::from_str("stable_diffusion").unwrap(), InferenceModelType::StableDiffusion);
assert_eq!(InferenceModelType::from_str("mocap_net").unwrap(), InferenceModelType::MocapNet);
assert_eq!(InferenceModelType::from_str("styletts2").unwrap(), InferenceModelType::StyleTTS2);
assert_eq!(InferenceModelType::from_str("comfy_ui").unwrap(), InferenceModelType::ComfyUi);
assert_eq!(InferenceModelType::from_str("convert_fbx_gltf").unwrap(), InferenceModelType::ConvertFbxToGltf);
}
#[test]
fn all_variants() {
// Static check
let mut variants = InferenceModelType::all_variants();
assert_eq!(variants.len(), 12);
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(), Some(InferenceModelType::StableDiffusion));
assert_eq!(variants.pop_first(), Some(InferenceModelType::MocapNet));
assert_eq!(variants.pop_first(), Some(InferenceModelType::ComfyUi));
assert_eq!(variants.pop_first(), Some(InferenceModelType::StyleTTS2));
assert_eq!(variants.pop_first(), Some(InferenceModelType::ConvertFbxToGltf));
assert_eq!(variants.pop_first(), None);
// Generated check
use strum::IntoEnumIterator;
assert_eq!(InferenceModelType::all_variants().len(), InferenceModelType::iter().len());
}
}
mod mechanical_checks {
use super::*;
#[test]
fn to_str() {
assert_eq!(InferenceModelType::RvcV2.to_str(), "rvc_v2");
assert_eq!(InferenceModelType::SadTalker.to_str(), "sad_talker");
assert_eq!(InferenceModelType::SoVitsSvc.to_str(), "so_vits_svc");
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");
assert_eq!(InferenceModelType::StableDiffusion.to_str(), "stable_diffusion");
assert_eq!(InferenceModelType::MocapNet.to_str(), "mocap_net");
assert_eq!(InferenceModelType::StyleTTS2.to_str(), "styletts2");
assert_eq!(InferenceModelType::ComfyUi.to_str(), "comfy_ui");
}
#[test]
fn variant_length() {
use strum::IntoEnumIterator;
assert_eq!(InferenceModelType::all_variants().len(), InferenceModelType::iter().len());
}
#[test]
fn from_str() {
assert_eq!(InferenceModelType::from_str("rvc_v2").unwrap(), InferenceModelType::RvcV2);
assert_eq!(InferenceModelType::from_str("sad_talker").unwrap(), InferenceModelType::SadTalker);
assert_eq!(InferenceModelType::from_str("so_vits_svc").unwrap(), InferenceModelType::SoVitsSvc);
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);
assert_eq!(InferenceModelType::from_str("stable_diffusion").unwrap(), InferenceModelType::StableDiffusion);
assert_eq!(InferenceModelType::from_str("mocap_net").unwrap(), InferenceModelType::MocapNet);
assert_eq!(InferenceModelType::from_str("styletts2").unwrap(), InferenceModelType::StyleTTS2);
assert_eq!(InferenceModelType::from_str("comfy_ui").unwrap(), InferenceModelType::ComfyUi);
}
#[test]
fn round_trip() {
for variant in InferenceModelType::all_variants() {
assert_eq!(variant, InferenceModelType::from_str(variant.to_str()).unwrap());
assert_eq!(variant, InferenceModelType::from_str(&format!("{}", variant)).unwrap());
assert_eq!(variant, InferenceModelType::from_str(&format!("{:?}", variant)).unwrap());
}
}
#[test]
fn all_variants() {
// Static check
let mut variants = InferenceModelType::all_variants();
assert_eq!(variants.len(), 11);
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(), Some(InferenceModelType::StableDiffusion));
assert_eq!(variants.pop_first(), Some(InferenceModelType::MocapNet));
assert_eq!(variants.pop_first(), Some(InferenceModelType::ComfyUi));
assert_eq!(variants.pop_first(), Some(InferenceModelType::StyleTTS2));
assert_eq!(variants.pop_first(), None);
// Generated check
use strum::IntoEnumIterator;
assert_eq!(InferenceModelType::all_variants().len(), InferenceModelType::iter().len());
#[test]
fn serialized_length_ok_for_database() {
const MAX_LENGTH : usize = 32;
for variant in InferenceModelType::all_variants() {
let serialized = variant.to_str();
assert!(serialized.len() > 0, "variant {:?} is too short", variant);
assert!(serialized.len() <= MAX_LENGTH, "variant {:?} is too long", variant);
}
}
}
}
@@ -12,7 +12,7 @@ use strum::EnumIter;
///
/// These types are present in the HTTP API and database columns as serialized here.
///
/// DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY.
/// YOU CAN ADD NEW VALUES, BUT DO NOT CHANGE EXISTING VALUES WITHOUT A MIGRATION STRATEGY.
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
pub enum InferenceResultType {
@@ -1,5 +1,6 @@
pub mod frontend_failure_category;
pub mod inference_category;
pub mod inference_input_source_token_type;
pub mod inference_job_type;
pub mod inference_model_type;
pub mod inference_result_type;
@@ -143,14 +143,14 @@ mod tests {
assert_eq!(MediaFileOriginModelType::from_str("mocap_net").unwrap(), MediaFileOriginModelType::MocapNet);
assert_eq!(MediaFileOriginModelType::from_str("comfy_ui").unwrap(), MediaFileOriginModelType::ComfyUi);
assert_eq!(MediaFileOriginModelType::from_str("styletts2").unwrap(), MediaFileOriginModelType::StyleTTS2);
assert_eq!(MediaFileOriginModelType::from_str("stable_diffusion_1_5").unwrap(), MediaFileOriginModelType::StyleTTS2);
assert_eq!(MediaFileOriginModelType::from_str("stable_diffusion_1_5").unwrap(), MediaFileOriginModelType::StableDiffusion15);
assert!(MediaFileOriginModelType::from_str("foo").is_err());
}
#[test]
fn all_variants() {
let mut variants = MediaFileOriginModelType::all_variants();
assert_eq!(variants.len(), 9);
assert_eq!(variants.len(), 10);
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::RvcV2));
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::SadTalker));
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::SoVitsSvc));
@@ -124,7 +124,7 @@ mod tests {
assert_serialization(MediaFileOriginProductCategory::ZeroShotVoice, "zs_voice");
assert_serialization(MediaFileOriginProductCategory::VideoFilter, "video_filter");
assert_serialization(MediaFileOriginProductCategory::Mocap, "mocap");
assert_serialization(MediaFileOriginProductCategory::Mocap, "image_gen");
assert_serialization(MediaFileOriginProductCategory::ImageGeneration, "image_gen");
assert_serialization(MediaFileOriginProductCategory::Workflow, "workflow");
}
@@ -6,7 +6,7 @@ use strum::EnumCount;
use strum::EnumIter;
use utoipa::ToSchema;
/// Used in the `media_files` table in a `VARCHAR` field.
/// Used in the `media_files` table in a `VARCHAR(16)` field.
///
/// DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY.
#[cfg_attr(test, derive(EnumIter, EnumCount))]
@@ -23,7 +23,22 @@ pub enum MediaFileType {
Video,
/// Mocap files: BVH, etc.
/// NB: In actuality, this is just the BVH file type.
/// NB: This is the old type to migrate from.
Mocap,
/// BVH files (for Bevy)
/// NB: This is the new type to migrate to.
Bvh,
/// FBX files (for Bevy)
Fbx,
/// glTF binary files (for Bevy)
Glb,
/// glTF files (for Bevy)
Gltf,
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
@@ -39,6 +54,10 @@ impl MediaFileType {
Self::Image => "image",
Self::Video => "video",
Self::Mocap => "mocap",
Self::Bvh => "bvh",
Self::Fbx => "fbx",
Self::Glb => "glb",
Self::Gltf => "gltf",
}
}
@@ -48,6 +67,10 @@ impl MediaFileType {
"image" => Ok(Self::Image),
"video" => Ok(Self::Video),
"mocap" => Ok(Self::Mocap),
"bvh" => Ok(Self::Bvh),
"fbx" => Ok(Self::Fbx),
"glb" => Ok(Self::Glb),
"gltf" => Ok(Self::Gltf),
_ => Err(format!("invalid value: {:?}", value)),
}
}
@@ -60,6 +83,10 @@ impl MediaFileType {
Self::Image,
Self::Video,
Self::Mocap,
Self::Bvh,
Self::Fbx,
Self::Glb,
Self::Gltf,
])
}
}
@@ -78,6 +105,10 @@ mod tests {
assert_serialization(MediaFileType::Image, "image");
assert_serialization(MediaFileType::Video, "video");
assert_serialization(MediaFileType::Mocap, "mocap");
assert_serialization(MediaFileType::Bvh, "bvh");
assert_serialization(MediaFileType::Fbx, "fbx");
assert_serialization(MediaFileType::Glb, "glb");
assert_serialization(MediaFileType::Gltf, "gltf");
}
}
@@ -89,7 +120,10 @@ mod tests {
assert_eq!(MediaFileType::Audio.to_str(), "audio");
assert_eq!(MediaFileType::Image.to_str(), "image");
assert_eq!(MediaFileType::Video.to_str(), "video");
assert_eq!(MediaFileType::Mocap.to_str(), "mocap");
assert_eq!(MediaFileType::Bvh.to_str(), "bvh");
assert_eq!(MediaFileType::Fbx.to_str(), "fbx");
assert_eq!(MediaFileType::Glb.to_str(), "glb");
assert_eq!(MediaFileType::Gltf.to_str(), "gltf");
}
#[test]
@@ -98,6 +132,10 @@ mod tests {
assert_eq!(MediaFileType::from_str("image").unwrap(), MediaFileType::Image);
assert_eq!(MediaFileType::from_str("video").unwrap(), MediaFileType::Video);
assert_eq!(MediaFileType::from_str("mocap").unwrap(), MediaFileType::Mocap);
assert_eq!(MediaFileType::from_str("bvh").unwrap(), MediaFileType::Bvh);
assert_eq!(MediaFileType::from_str("fbx").unwrap(), MediaFileType::Fbx);
assert_eq!(MediaFileType::from_str("glb").unwrap(), MediaFileType::Glb);
assert_eq!(MediaFileType::from_str("gltf").unwrap(), MediaFileType::Gltf);
assert!(MediaFileType::from_str("foo").is_err());
}
}
@@ -108,11 +146,15 @@ mod tests {
#[test]
fn all_variants() {
let mut variants = MediaFileType::all_variants();
assert_eq!(variants.len(), 4);
assert_eq!(variants.len(), 8);
assert_eq!(variants.pop_first(), Some(MediaFileType::Audio));
assert_eq!(variants.pop_first(), Some(MediaFileType::Image));
assert_eq!(variants.pop_first(), Some(MediaFileType::Video));
assert_eq!(variants.pop_first(), Some(MediaFileType::Mocap));
assert_eq!(variants.pop_first(), Some(MediaFileType::Bvh));
assert_eq!(variants.pop_first(), Some(MediaFileType::Fbx));
assert_eq!(variants.pop_first(), Some(MediaFileType::Glb));
assert_eq!(variants.pop_first(), Some(MediaFileType::Gltf));
assert_eq!(variants.pop_first(), None);
}
}
@@ -134,5 +176,15 @@ mod tests {
assert_eq!(variant, MediaFileType::from_str(&format!("{:?}", variant)).unwrap());
}
}
#[test]
fn serialized_length_ok_for_database() {
const MAX_LENGTH : usize = 16;
for variant in MediaFileType::all_variants() {
let serialized = variant.to_str();
assert!(serialized.len() > 0, "variant {:?} is too short", variant);
assert!(serialized.len() <= MAX_LENGTH, "variant {:?} is too long", variant);
}
}
}
}
+6 -11
View File
@@ -7,12 +7,7 @@ use errors::AnyhowResult;
use crate::bucket_clients::get_bucket_clients;
use crate::cli_args::parse_cli_args;
use crate::seeding::model_weights::seed_weights;
use crate::seeding::media_files::seed_media_seedtool;
use crate::seeding::seed_media_files::seed_media_files;
use crate::seeding::tts_tacotron2::seed_tts_tacotron2;
use crate::seeding::users::seed_user_accounts;
use crate::seeding::voice_conversion::seed_voice_conversion;
use crate::seeding::seed_model_weights_for_local::seed_weights;
pub mod bucket_clients;
@@ -52,13 +47,13 @@ pub async fn main() -> AnyhowResult<()> {
// maybe_elasticsearch = Some(get_elasticsearch_client());
// }
seed_user_accounts(&pool).await?;
seed_media_files(&pool, maybe_bucket_clients.as_ref()).await?;
// seed_user_accounts(&pool).await?;
// seed_media_files(&pool, maybe_bucket_clients.as_ref()).await?;
//seed_zero_shot_tts(&pool, maybe_bucket_clients.as_ref()).await?;
seed_voice_conversion(&pool).await?;
// seed_voice_conversion(&pool).await?;
seed_weights(&pool).await?;
seed_media_seedtool(&pool).await?;
seed_tts_tacotron2(&pool, maybe_bucket_clients.as_ref()).await?;
// seed_media_seedtool(&pool).await?;
// seed_tts_tacotron2(&pool, maybe_bucket_clients.as_ref()).await?;
// should seed the weights with a few files for hanashi
//seed_weights_files(&pool, maybe_bucket_clients.as_ref()).await?;
@@ -43,7 +43,7 @@ pub async fn seed_test_videos(mysql_pool: &Pool<MySql>, user_token: UserToken) -
}
pub async fn seed_media_seedtool(mysql_pool: &Pool<MySql>) -> AnyhowResult<()> {
info!("Seeding weights...");
info!("Seeding media...");
let user_token = match get_user_token_by_username(HANASHI_USERNAME, mysql_pool).await? {
None => {
@@ -1,4 +1,4 @@
pub mod model_weights;
pub mod seed_model_weights_for_local;
pub mod seed_media_files;
pub mod tts_tacotron2;
pub mod users;
@@ -137,7 +137,7 @@ pub async fn seed_weights_files(
maybe_last_update_user_token: None, // Assuming there is no last update user token
original_download_url: Some(original_download_url.to_string()),
original_filename: Some(original_filename.to_string()),
file_size_bytes: file_size_bytes as i32, // Assuming the file size is 0
file_size_bytes, // Assuming the file size is 0
file_checksum_sha2: sha256_checksum.to_string(), // Assuming the file checksum is an empty string
public_bucket_hash: bucket_hash,
maybe_public_bucket_prefix: Some(maybe_bucket_prefix.to_string()),
@@ -6,7 +6,7 @@ use sqlx::{MySql, Pool};
use cloud_storage::remote_file_manager::remote_cloud_bucket_details::RemoteCloudBucketDetails;
use cloud_storage::remote_file_manager::remote_cloud_file_manager::RemoteCloudFileClient;
use cloud_storage::remote_file_manager::weights_descriptor::{WeightsLoRADescriptor, WeightsSD15Descriptor, WeightsWorkflowDescriptor};
use cloud_storage::remote_file_manager::weights_descriptor::{WeightsLoRADescriptor, WeightsSD15Descriptor, WeightsSD15CkptDescriptor, WeightsWorkflowDescriptor};
use enums::by_table::model_weights::{
weights_category::WeightsCategory,
weights_types::WeightsType,
@@ -126,7 +126,7 @@ pub async fn seed_weights_for_paging(mysql_pool: &Pool<MySql>, user_token: UserT
// create a loop that loops from 1 to 100
for i in 1..=100 {
// create a new weight
let weights_category = match i {
1..=20 => WeightsCategory::VoiceConversion,
@@ -156,11 +156,6 @@ pub async fn seed_weights_for_paging(mysql_pool: &Pool<MySql>, user_token: UserT
let _private_bucket_hash = "".to_string();
let private_bucket_prefix;
let private_bucket_extension;
//let mut cached_user_ratings_total_count;
//let mut cached_user_ratings_positive_count;
//let mut cached_user_ratings_negative_count;
//let mut cached_user_ratings_ratio;
let version;
match i {
@@ -179,14 +174,8 @@ pub async fn seed_weights_for_paging(mysql_pool: &Pool<MySql>, user_token: UserT
original_filename = format!("gura_gwar{}.safetensors", i);
original_download_url = format!("www.google.ca");
// private_bucket_hash = format!("bucket_hash{}", i);
private_bucket_prefix = format!("_fake");
private_bucket_extension = format!("rvcV2");
//cached_user_ratings_total_count = i;
//cached_user_ratings_positive_count = i;
//cached_user_ratings_negative_count = i;
//cached_user_ratings_ratio = i as u32 / 100;
version = i as i32;
println!("Seeding RVCv2 model {}", i);
},
@@ -204,18 +193,10 @@ pub async fn seed_weights_for_paging(mysql_pool: &Pool<MySql>, user_token: UserT
"#;
description_rendered_html = tts_markdown_description;
original_filename = format!("filename{}.txt", i);
original_download_url = format!("https://civitai.com/api/download/models/149193?type=Model&format=SafeTensor&size=pruned&fp=fp16");
// private_bucket_hash = format!("bucket_hash{}", i);
private_bucket_prefix = format!("_fake");
private_bucket_extension = format!("tt2");
//cached_user_ratings_total_count = i;
//cached_user_ratings_positive_count = i;
//cached_user_ratings_negative_count = i;
//cached_user_ratings_ratio = i as u32 / 100;
version = i as i32;
println!("Seeding TT2 model {}", i);
},
@@ -223,22 +204,12 @@ pub async fn seed_weights_for_paging(mysql_pool: &Pool<MySql>, user_token: UserT
// SD 1.5
model_weight_token = ModelWeightToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
title = format!("dragonfruitGTv1: {}", i);
description = format!("Description Number {}:", i);
description_rendered_html = sd1_5_markdown_description;
original_filename = format!("filename{}.txt", i);
original_download_url = format!("https://civitai.com/api/download/models/149193?type=Model&format=SafeTensor&size=pruned&fp=fp16");
// private_bucket_hash = format!("bucket_hash{}", i);
private_bucket_prefix = format!("_fake");
private_bucket_extension = format!("sd15");
//cached_user_ratings_total_count = i;
//cached_user_ratings_positive_count = i;
//cached_user_ratings_negative_count = i;
//cached_user_ratings_ratio = i as u32 / 100;
version = i as i32;
println!("Seeding SD15 model {}", i);
},
@@ -262,24 +233,14 @@ pub async fn seed_weights_for_paging(mysql_pool: &Pool<MySql>, user_token: UserT
Have fun! 😊
"#;
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::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
title = format!("SDXL_Niji_Special Edition: {}", i);
description = format!("Description Number {}:", i);
// private_bucket_hash = format!("bucket_hash{}", i);
private_bucket_prefix = format!("_fake");
private_bucket_extension = format!("sdxl");
description_rendered_html = sdxl_markdown_description;
original_filename = format!("filename{}.txt", i);
original_download_url = format!("https://civitai.com/api/download/models/149193?type=Model&format=SafeTensor&size=pruned&fp=fp16");
//cached_user_ratings_total_count = i;
//cached_user_ratings_positive_count = i;
//cached_user_ratings_negative_count = i;
//cached_user_ratings_ratio = i as u32 / 100;
version = i as i32;
println!("Seeding SDXL model {}", i);
},
@@ -289,26 +250,15 @@ pub async fn seed_weights_for_paging(mysql_pool: &Pool<MySql>, user_token: UserT
- **Improved Quality:** The special edition offers significantly better quality with fewer issues. It's the model as initially envisioned.
- LoRA Gawr Gura
"#;
let _lora_image_token = "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/123645df-dee2-4239-863a-76a150b09c32/width=1024/00000-2171948503.jpeg";
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);
description_rendered_html = lora_markdown_description;
original_filename = format!("filename{}.txt", i);
original_download_url = format!("https://civitai.com/api/download/models/149193?type=Model&format=SafeTensor&size=pruned&fp=fp16");
// private_bucket_hash = format!("bucket_hash{}", i);
private_bucket_prefix = format!("_fake");
private_bucket_extension = format!("loRA");
//cached_user_ratings_total_count = i;
//cached_user_ratings_positive_count = i;
//cached_user_ratings_negative_count = i;
//cached_user_ratings_ratio = i as u32 / 100;
version = i as i32;
println!("Seeding LoRA model {}", i);
},
@@ -319,22 +269,14 @@ pub async fn seed_weights_for_paging(mysql_pool: &Pool<MySql>, user_token: UserT
- **Improved Quality:** The special edition offers significantly better quality with fewer issues. It's the model as initially envisioned.
- Gawr Gura MOS 4
"#;
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;
original_filename = format!("gura_gwar{}.safetensors", i);
original_download_url = format!("www.google.ca");
// private_bucket_hash = format!("bucket_hash{}", i);
private_bucket_prefix = format!("_fake");
private_bucket_extension = format!("rvcV2");
//cached_user_ratings_total_count = i;
//cached_user_ratings_positive_count = i;
//cached_user_ratings_negative_count = i;
//cached_user_ratings_ratio = i as u32 / 100;
version = i as i32;
println!("Out of range");
@@ -359,11 +301,7 @@ pub async fn seed_weights_for_paging(mysql_pool: &Pool<MySql>, user_token: UserT
public_bucket_hash: "bucket_hash1".to_string(),
maybe_public_bucket_prefix: Some(private_bucket_prefix),
maybe_public_bucket_extension: Some(private_bucket_extension),
//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 as f32),
version,
version: version as u32,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
};
@@ -590,6 +528,9 @@ Noosphere by skumerz + dalcefoPainting + 饭特稀V08 by zhazhahui345 + GhostMix
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_weight_token6 = ModelWeightToken::generate_for_testing_and_dev_seeding_never_use_in_production_seriously();
let remote_cloud_file_client = RemoteCloudFileClient::get_remote_cloud_file_client().await?;
@@ -597,6 +538,11 @@ Noosphere by skumerz + dalcefoPainting + 饭特稀V08 by zhazhahui345 + GhostMix
let lora_descriptor = Box::new(WeightsLoRADescriptor{});
let sd_vae_15_weights_descriptor = Box::new(WeightsSD15Descriptor {});
let sd_15_weights_descriptor2 = Box::new(WeightsSD15Descriptor {});
let lora_descriptor2 = Box::new(WeightsLoRADescriptor{});
let sd_vae_15_weights_descriptor2 = Box::new(WeightsSD15Descriptor {});
let mut path_object_SD = get_seed_tool_data_root();
path_object_SD.push("models/imagegen/sd15/majicmixFantasy_v30Vae.safetensors");
@@ -606,10 +552,26 @@ Noosphere by skumerz + dalcefoPainting + 饭特稀V08 by zhazhahui345 + GhostMix
let mut path_to_VAE = get_seed_tool_data_root();
path_to_VAE.push("models/imagegen/vae/zVae_v20.safetensors");
// anime model
let mut path_object_SD_anime = get_seed_tool_data_root();
path_object_SD_anime.push("models/imagegen/sd15/animerge_v26.safetensors");
// hongkai clara
let mut path_object_loRA2 = get_seed_tool_data_root();
path_object_loRA2.push("models/imagegen/loRA/Clara_Honkai_Star_Rail_v2-10.safetensors");
// anime vae
let mut path_to_VAE2 = get_seed_tool_data_root();
path_to_VAE2.push("models/imagegen/vae/vaeFtMse840000EmaPruned_vae.safetensors");
let metadata1 = remote_cloud_file_client.upload_file(sd_15_weights_descriptor,path_object_SD.as_path().to_str().unwrap()).await?;
let metadata2 = remote_cloud_file_client.upload_file(lora_descriptor,path_object_loRA.as_path().to_str().unwrap()).await?;
let metadata3 = remote_cloud_file_client.upload_file(sd_vae_15_weights_descriptor,path_to_VAE.as_path().to_str().unwrap()).await?;
let metadata4 = remote_cloud_file_client.upload_file(sd_15_weights_descriptor2,path_object_SD_anime.as_path().to_str().unwrap()).await?;
let metadata5 = remote_cloud_file_client.upload_file(lora_descriptor2,path_object_loRA2.as_path().to_str().unwrap()).await?;
let metadata6 = remote_cloud_file_client.upload_file(sd_vae_15_weights_descriptor2,path_to_VAE2.as_path().to_str().unwrap()).await?;
let weights1 = CreateModelWeightsArgs {
token: &model_weight_token1, // replace with actual ModelWeightToken
weights_type: WeightsType::StableDiffusion15, // replace with actual WeightsType
@@ -623,7 +585,7 @@ Noosphere by skumerz + dalcefoPainting + 饭特稀V08 by zhazhahui345 + GhostMix
maybe_last_update_user_token: Some("Last Update User Token".to_string()),
original_download_url: Some("https://civitai.com/models/41865/majicmix-fantasy".to_string()),
original_filename: Some("majicmixFantasy_v30Vae.safetensors".to_string()),
file_size_bytes: metadata1.file_size_bytes as i32,
file_size_bytes: 0,
file_checksum_sha2: metadata1.sha256_checksum.to_string(),
public_bucket_hash: metadata1.bucket_details.clone().unwrap().object_hash,
maybe_public_bucket_prefix: Some(metadata1.bucket_details.clone().unwrap().prefix),
@@ -645,7 +607,7 @@ Noosphere by skumerz + dalcefoPainting + 饭特稀V08 by zhazhahui345 + GhostMix
maybe_last_update_user_token: Some("<p> Honkai <p>".to_string()),
original_download_url: Some("https://civitai.com/models/95243/mihoyo-collection-honkai-impact-3rd-or-honkai-star-rail-or-genshin-impact-or-zenless-zone-zero".to_string()),
original_filename: Some("xiawolei-v100-000019.safetensors".to_string()),
file_size_bytes: metadata2.file_size_bytes as i32,
file_size_bytes: 0,
file_checksum_sha2: metadata2.sha256_checksum.to_string(),
public_bucket_hash: metadata2.bucket_details.clone().unwrap().object_hash.clone(),
maybe_public_bucket_prefix: Some(metadata2.bucket_details.clone().unwrap().prefix),
@@ -667,7 +629,7 @@ Noosphere by skumerz + dalcefoPainting + 饭特稀V08 by zhazhahui345 + GhostMix
maybe_last_update_user_token: Some("Last Update User Token".to_string()),
original_download_url: Some("https://civitai.com/models/97653/z-vae".to_string()),
original_filename: Some("zVae_v20.safetensors".to_string()),
file_size_bytes: metadata3.file_size_bytes as i32,
file_size_bytes: 0,
file_checksum_sha2: metadata3.sha256_checksum.to_string(),
public_bucket_hash: metadata3.bucket_details.clone().unwrap().object_hash,
maybe_public_bucket_prefix: Some(metadata3.bucket_details.clone().unwrap().prefix),
@@ -676,42 +638,109 @@ Noosphere by skumerz + dalcefoPainting + 饭特稀V08 by zhazhahui345 + GhostMix
mysql_pool: &mysql_pool, // replace with actual MySqlPool
};
let weights4 = CreateModelWeightsArgs {
token: &model_weight_token4, // replace with actual ModelWeightToken
weights_type: WeightsType::StableDiffusion15, // replace with actual WeightsType
weights_category: WeightsCategory::ImageGeneration, // replace with actual WeightsCategory
title: "animerge_v26".to_string(),
maybe_description_markdown: Some(sd1_5_markdown_description.to_string()),
maybe_description_rendered_html: Some("<p>Description</p>".to_string()),
creator_user_token: Some(&user_token), // replace with actual UserToken
creator_ip_address: "192.168.1.1",
creator_set_visibility: Visibility::Public,
maybe_last_update_user_token: Some("Last Update User Token".to_string()),
original_download_url: Some("https://civitai.com/models/144249/animerge".to_string()),
original_filename: Some("".to_string()),
file_size_bytes: 0,
file_checksum_sha2: metadata4.sha256_checksum.to_string(),
public_bucket_hash: metadata4.bucket_details.clone().unwrap().object_hash,
maybe_public_bucket_prefix: Some(metadata4.bucket_details.clone().unwrap().prefix),
maybe_public_bucket_extension: Some(metadata4.bucket_details.clone().unwrap().suffix),
version: 1,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
};
let weights5 = CreateModelWeightsArgs {
token: &model_weight_token5, // replace with actual ModelWeightToken
weights_type: WeightsType::LoRA, // replace with actual WeightsType
weights_category: WeightsCategory::ImageGeneration, // replace with actual WeightsCategory
title: "clara LoRA".to_string(),
maybe_description_markdown: Some(miyhoyo_description.to_string()),
maybe_description_rendered_html: Some(miyhoyo_description.to_string()),
creator_user_token: Some(&user_token), // replace with actual UserToken
creator_ip_address: "292.268.2.2",
creator_set_visibility: Visibility::Public,
maybe_last_update_user_token: Some("<p> Honkai <p>".to_string()),
original_download_url: Some("https://civitai.com/models/56454/clara-honkai-star-rail-lora".to_string()),
original_filename: Some("clara.safetensors".to_string()),
file_size_bytes: 0,
file_checksum_sha2: metadata5.sha256_checksum.to_string(),
public_bucket_hash: metadata5.bucket_details.clone().unwrap().object_hash.clone(),
maybe_public_bucket_prefix: Some(metadata5.bucket_details.clone().unwrap().prefix),
maybe_public_bucket_extension: Some(metadata5.bucket_details.clone().unwrap().suffix),
version: 2,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
};
// some VAE
let weights6 = CreateModelWeightsArgs {
token: &model_weight_token6, // replace with actual ModelWeightToken
weights_type: WeightsType::StableDiffusion15, // replace with actual WeightsType
weights_category: WeightsCategory::ImageGeneration, // replace with actual WeightsCategory
title: "anime-vae".to_string(),
maybe_description_markdown: Some(sd_vae_description.to_string()),
maybe_description_rendered_html: Some("<p>Description</p>".to_string()),
creator_user_token: Some(&user_token), // replace with actual UserToken
creator_ip_address: "192.168.1.1",
creator_set_visibility: Visibility::Public,
maybe_last_update_user_token: Some("Last Update User Token".to_string()),
original_download_url: Some("https://civitai.com/models/97653/????".to_string()),
original_filename: Some("zVae_v20.safetensors".to_string()),
file_size_bytes: 0,
file_checksum_sha2: metadata6.sha256_checksum.to_string(),
public_bucket_hash: metadata6.bucket_details.clone().unwrap().object_hash,
maybe_public_bucket_prefix: Some(metadata6.bucket_details.clone().unwrap().prefix),
maybe_public_bucket_extension: Some(metadata6.bucket_details.clone().unwrap().suffix),
version: 1,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
};
create_weight(weights1).await?;
create_weight(weights2).await?;
create_weight(weights3).await?;
create_weight(weights4).await?;
create_weight(weights5).await?;
create_weight(weights6).await?;
Ok(())
}
pub async fn seed_workflows_for_testing_inference(mysql_pool: &Pool<MySql>, user_token: UserToken) -> AnyhowResult<()>{
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 mut path_to_comfy = get_seed_tool_data_root();
path_to_comfy.push("models/workflows/comfyui/workflow_api.json");
path_to_comfy.push("models/workflows/comfyui/yae_video_prod.json");
let remote_cloud_file_client = RemoteCloudFileClient::get_remote_cloud_file_client().await?;
let comfy_weights_descriptor = Box::new(WeightsWorkflowDescriptor {});
let metadata1 = remote_cloud_file_client.upload_file(comfy_weights_descriptor, path_to_comfy.as_path().to_str().unwrap()).await?;
let mut path_to_comfy2 = get_seed_tool_data_root();
path_to_comfy2.push("models/workflows/comfyui/majicmixRealistic_v7.safetensors");
let comfy_weights_descriptor2 = Box::new(WeightsSD15Descriptor {});
let metadata2 = remote_cloud_file_client.upload_file(comfy_weights_descriptor2, path_to_comfy2.as_path().to_str().unwrap()).await?;
let weights1 = CreateModelWeightsArgs {
token: &model_weight_token1, // replace with actual ModelWeightToken
weights_type: WeightsType::ComfyUi, // replace with actual WeightsType
weights_category: WeightsCategory::WorkflowConfig, // replace with actual WeightsCategory
title: "comfy-test-workflow".to_string(),
maybe_description_markdown: Some("Test workflow for ComfyUI".to_string()),
title: "yae-video-prod".to_string(),
maybe_description_markdown: Some("ComfyUI Workflow".to_string()),
maybe_description_rendered_html: Some("<p>Description</p>".to_string()),
creator_user_token: Some(&user_token), // replace with actual UserToken
creator_ip_address: "192.168.1.1",
creator_set_visibility: Visibility::Public,
maybe_last_update_user_token: Some("Last Update User Token".to_string()),
original_download_url: Some("https://github.com/comfyanonymous/ComfyUI".to_string()),
original_filename: Some("test-workflow.json".to_string()),
file_size_bytes: metadata1.file_size_bytes as i32,
original_filename: Some("yae_video_prod.json".to_string()),
file_size_bytes: metadata1.file_size_bytes,
file_checksum_sha2: metadata1.sha256_checksum.to_string(),
public_bucket_hash: metadata1.bucket_details.clone().unwrap().object_hash,
maybe_public_bucket_prefix: Some(metadata1.bucket_details.clone().unwrap().prefix),
@@ -719,30 +748,9 @@ pub async fn seed_workflows_for_testing_inference(mysql_pool: &Pool<MySql>, user
version: 1,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
};
let weights2 = CreateModelWeightsArgs {
token: &model_weight_token2, // replace with actual ModelWeightToken
weights_type: WeightsType::ComfyUi, // replace with actual WeightsType
weights_category: WeightsCategory::WorkflowConfig, // replace with actual WeightsCategory
title: "v1-5-pruned-emaonly".to_string(),
maybe_description_markdown: Some("Test model for ComfyUI".to_string()),
maybe_description_rendered_html: Some("<p>Description</p>".to_string()),
creator_user_token: Some(&user_token), // replace with actual UserToken
creator_ip_address: "192.168.1.1",
creator_set_visibility: Visibility::Public,
maybe_last_update_user_token: Some("Last Update User Token".to_string()),
original_download_url: Some("https://huggingface.co/runwayml/stable-diffusion-v1-5".to_string()),
original_filename: Some("v1-5-pruned-emaonly.ckpt".to_string()),
file_size_bytes: metadata2.file_size_bytes as i32,
file_checksum_sha2: metadata2.sha256_checksum.to_string(),
public_bucket_hash: metadata2.bucket_details.clone().unwrap().object_hash,
maybe_public_bucket_prefix: Some(metadata2.bucket_details.clone().unwrap().prefix),
maybe_public_bucket_extension: Some(metadata2.bucket_details.clone().unwrap().suffix),
version: 1,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
};
create_weight(weights1).await?;
create_weight(weights2).await?;
Ok(())
}
@@ -780,10 +788,10 @@ pub async fn seed_weights_for_user_token(
public_bucket_hash: "bucket_hash1".to_string(),
maybe_public_bucket_prefix: Some("_fake".to_string()),
maybe_public_bucket_extension: Some("rvc".to_string()),
//cached_user_ratings_total_count: 10,
//cached_user_ratings_positive_count: 9,
//cached_user_ratings_negative_count: 1,
//maybe_cached_user_ratings_ratio: Some(0.9),
version: 1,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
},
@@ -805,10 +813,10 @@ pub async fn seed_weights_for_user_token(
public_bucket_hash: "bucket_hash2".to_string(),
maybe_public_bucket_prefix: Some("_fake".to_string()),
maybe_public_bucket_extension: Some("tt2".to_string()),
//cached_user_ratings_total_count: 20,
//cached_user_ratings_positive_count: 9,
//cached_user_ratings_negative_count: 2,
//maybe_cached_user_ratings_ratio: Some(0.9),
version: 2,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
},
@@ -830,10 +838,10 @@ pub async fn seed_weights_for_user_token(
public_bucket_hash: "bucket_hash3".to_string(),
maybe_public_bucket_prefix: Some("_fake".to_string()),
maybe_public_bucket_extension: Some("safetensors".to_string()),
//cached_user_ratings_total_count: 10,
//cached_user_ratings_positive_count: 9,
//cached_user_ratings_negative_count: 1,
//maybe_cached_user_ratings_ratio: Some(0.9),
version: 1,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
},
@@ -855,10 +863,10 @@ pub async fn seed_weights_for_user_token(
public_bucket_hash: "bucket_hash4".to_string(),
maybe_public_bucket_prefix: Some("_fake".to_string()),
maybe_public_bucket_extension: Some("LoRA".to_string()),
//cached_user_ratings_total_count: 10,
//cached_user_ratings_positive_count: 9,
//cached_user_ratings_negative_count: 1,
//maybe_cached_user_ratings_ratio: Some(0.9),
version: 2,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
},
@@ -880,10 +888,10 @@ pub async fn seed_weights_for_user_token(
public_bucket_hash: "bucket_hash4".to_string(),
maybe_public_bucket_prefix: Some("_fake".to_string()),
maybe_public_bucket_extension: Some("LoRA".to_string()),
//cached_user_ratings_total_count: 10,
//cached_user_ratings_positive_count: 9,
//cached_user_ratings_negative_count: 1,
//maybe_cached_user_ratings_ratio: Some(0.9),
version: 2,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
}
@@ -931,10 +939,10 @@ pub async fn original_seed_weights(mysql_pool: &Pool<MySql>, _user_token: UserTo
public_bucket_hash: "bucket_hash1".to_string(),
maybe_public_bucket_prefix: Some("_fake".to_string()),
maybe_public_bucket_extension: Some("rvc".to_string()),
//cached_user_ratings_total_count: 10,
//cached_user_ratings_positive_count: 9,
//cached_user_ratings_negative_count: 1,
//maybe_cached_user_ratings_ratio: Some(0.9),
version: 1,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
},
@@ -956,10 +964,10 @@ pub async fn original_seed_weights(mysql_pool: &Pool<MySql>, _user_token: UserTo
public_bucket_hash: "bucket_hash2".to_string(),
maybe_public_bucket_prefix: Some("_fake".to_string()),
maybe_public_bucket_extension: Some("tt2".to_string()),
//cached_user_ratings_total_count: 20,
//cached_user_ratings_positive_count: 9,
//cached_user_ratings_negative_count: 2,
//maybe_cached_user_ratings_ratio: Some(0.9),
version: 2,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
},
@@ -981,10 +989,10 @@ pub async fn original_seed_weights(mysql_pool: &Pool<MySql>, _user_token: UserTo
public_bucket_hash: "bucket_hash3".to_string(),
maybe_public_bucket_prefix: Some("_fake".to_string()),
maybe_public_bucket_extension: Some("safetensors".to_string()),
//cached_user_ratings_total_count: 10,
//cached_user_ratings_positive_count: 9,
//cached_user_ratings_negative_count: 1,
//maybe_cached_user_ratings_ratio: Some(0.9),
version: 1,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
},
@@ -1006,10 +1014,10 @@ pub async fn original_seed_weights(mysql_pool: &Pool<MySql>, _user_token: UserTo
public_bucket_hash: "bucket_hash4".to_string(),
maybe_public_bucket_prefix: Some("_fake".to_string()),
maybe_public_bucket_extension: Some("LoRA".to_string()),
//cached_user_ratings_total_count: 10,
//cached_user_ratings_positive_count: 9,
//cached_user_ratings_negative_count: 1,
//maybe_cached_user_ratings_ratio: Some(0.9),
version: 2,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
},
@@ -1031,10 +1039,10 @@ pub async fn original_seed_weights(mysql_pool: &Pool<MySql>, _user_token: UserTo
public_bucket_hash: "bucket_hash5".to_string(),
maybe_public_bucket_prefix: Some("_fake".to_string()),
maybe_public_bucket_extension: Some("LoRA".to_string()),
//cached_user_ratings_total_count: 10,
//cached_user_ratings_positive_count: 9,
//cached_user_ratings_negative_count: 1,
//maybe_cached_user_ratings_ratio: Some(0.9),
version: 2,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
}
@@ -1062,8 +1070,8 @@ pub async fn seed_weights(mysql_pool: &Pool<MySql>) -> AnyhowResult<()> {
// seed_weights_for_user_token(mysql_pool, user_token).await?;
// seed_weights_for_paging(mysql_pool,user_token).await?;
seed_weights_for_testing_inference(mysql_pool,user_token.clone()).await?;
println!("TESTING DOWLOAD");
test_seed_weights_files().await?;
seed_workflows_for_testing_inference(mysql_pool,user_token.clone()).await?;
// println!("TESTING DOWLOAD");
// test_seed_weights_files().await?;
//seed_workflows_for_testing_inference(mysql_pool,user_token.clone()).await?;
Ok(())
}
@@ -1,4 +1,5 @@
use crate::remote_file_manager::file_descriptor::FileDescriptor;
// TODO ASK BRANDON WHAT path should this be?
const MEDIA_FILE_DIRECTORY: &str = "/media";
@@ -38,3 +39,21 @@ impl FileDescriptor for MediaVideoMp4Descriptor {
true
}
}
pub struct UploadVideoMp4Descriptor;
impl FileDescriptor for UploadVideoMp4Descriptor {
fn remote_directory_path(&self) -> &str {
MEDIA_FILE_DIRECTORY
}
fn get_suffix(&self)->String {
"mp4".to_string()
}
fn get_prefix(&self)->String {
"upload".to_string()
}
fn is_public(&self) -> bool {
true
}
}
@@ -31,7 +31,12 @@ impl RemoteCloudBucketDetails {
match self.prefix.as_str() {
// Weights
"loRA" => Box::new(weights_descriptor::WeightsLoRADescriptor {}),
"sd15" => Box::new(weights_descriptor::WeightsSD15Descriptor {}),
"sd15" =>
match self.suffix.as_str() {
"safetensors" => Box::new(weights_descriptor::WeightsSD15Descriptor {}),
"ckpt" => Box::new(weights_descriptor::WeightsSD15CkptDescriptor {}),
_ => panic!("Unknown suffix: {}",self.suffix)
},
"sdxl" => Box::new(weights_descriptor::WeightsSDXLDescriptor {}),
"valle_prompt" => Box::new(weights_descriptor::WeightsVallePromptDescriptor {}),
"rvc" => {
@@ -56,6 +61,12 @@ impl RemoteCloudBucketDetails {
_ => panic!("Unknown suffix: {}",self.suffix)
}
},
"upload" => {
match self.suffix.as_str() {
"mp4" => Box::new(media_descriptor::UploadVideoMp4Descriptor {}),
_ => panic!("Unknown suffix: {}",self.suffix)
}
},
_ => panic!("Unknown prefix: {}", self.prefix)
}
}
@@ -1,4 +1,5 @@
use anyhow::anyhow;
use buckets::public::media_files::bucket_file_path::MediaFileBucketPath;
use errors::AnyhowResult;
use filesys::file_read_bytes::file_read_bytes;
@@ -49,6 +50,14 @@ impl RemoteCloudFileClient {
Ok(())
}
pub async fn download_media_file(&self, media_file_path: &MediaFileBucketPath, to_system_file_path: String) -> AnyhowResult<()> {
const IS_PUBLIC: bool = true; // NB: Media files are always public
let full_remote_cloud_file_path = media_file_path.get_full_object_path_str().to_string();
self.bucket_orchestration_client.download_file_to_disk(full_remote_cloud_file_path, to_system_file_path, IS_PUBLIC).await?;
Ok(())
}
// return error or success with meta data.
pub async fn upload_file(&self, file_descriptor:Box<dyn FileDescriptor>, from_system_file_path:&str) -> AnyhowResult<FileMetaData> {
@@ -50,6 +50,30 @@ impl FileDescriptor for WeightsSD15Descriptor {
}
}
pub struct WeightsSD15CkptDescriptor;
impl FileDescriptor for WeightsSD15CkptDescriptor {
fn remote_directory_path(&self) -> &str {
REMOTE_FILE_DIRECTORY
}
// this will be the type of file peroid is handled by the file formatter
// e.g safetensors bin jpg
fn get_suffix(&self)->String {
"ckpt".to_string()
}
// This will be the prefix of the media type or the weights type.
// name of the weights or the name of the media type
// vall-e_prompt, SD15, sd15, sdxl when implmenting add to the end
fn get_prefix(&self)->String {
"sd15".to_string()
}
// This will be ensure that the right bucket is picked
fn is_public(&self) -> bool {
true
}
}
pub struct WeightsSDXLDescriptor;
@@ -189,4 +213,4 @@ impl FileDescriptor for WeightsWorkflowDescriptor {
fn is_public(&self) -> bool {
true
}
}
}
+17 -2
View File
@@ -13,7 +13,10 @@ pub enum EnvError {
reason: String
},
/// The required environment variable wasn't present.
RequiredNotPresent,
RequiredNotPresent {
/// The name of the missing environment variable.
name: String
},
}
impl Display for EnvError {
@@ -21,7 +24,19 @@ impl Display for EnvError {
let reason = match self {
EnvError::NotUnicode => "EnvError::NotUnicode",
EnvError::ParseError { .. } => "EnvError::ParseError",
EnvError::RequiredNotPresent => "EnvError::RequiredNotPresent",
EnvError::RequiredNotPresent { name } =>
return write!(f, r#"
EnvError::RequiredNotPresent: the following environment variable was not present:
---> {:?}
In development, setting it in the environment config files: .env, .env-secrets,
cargo/service/../{{app_name}}/config/{{app_name}}.development.env, etc.
If it looks like it might be a secret, ask for help in our company Discord.
In production, make sure these environment variables are set in Kubernetes.
"#, name),
};
write!(f, "{:?}", reason)
}
+1 -1
View File
@@ -51,7 +51,7 @@ pub fn get_env_bool_required(env_name: &str) -> Result<bool, EnvError> {
.and_then(|maybe| match maybe {
None => {
warn!("Env var '{}' not supplied.", env_name);
Err(EnvError::RequiredNotPresent)
Err(EnvError::RequiredNotPresent { name: env_name.to_string() })
},
Some(val) => Ok(val),
})
+1 -1
View File
@@ -12,7 +12,7 @@ pub fn get_env_duration_seconds_required(env_name: &str) -> Result<Duration, Env
.and_then(|maybe| match maybe {
None => {
warn!("Env var '{}' not supplied.", env_name);
Err(EnvError::RequiredNotPresent)
Err(EnvError::RequiredNotPresent { name: env_name.to_string() })
},
Some(val) => Ok(val),
})
+1 -1
View File
@@ -15,7 +15,7 @@ pub fn get_env_pathbuf_required(env_name: &str) -> Result<PathBuf, EnvError> {
.and_then(|maybe| match maybe {
None => {
warn!("Env var '{}' not supplied.", env_name);
Err(EnvError::RequiredNotPresent)
Err(EnvError::RequiredNotPresent { name: env_name.to_string() })
},
Some(val) => Ok(val),
})
+1 -1
View File
@@ -32,7 +32,7 @@ pub fn get_env_string_required(env_name: &str) -> Result<String, EnvError> {
Some(s) => Ok(s.to_string()),
None => {
warn!("Required env var '{}' not supplied.", env_name);
Err(EnvError::RequiredNotPresent)
Err(EnvError::RequiredNotPresent { name: env_name.to_string() })
},
}
}
@@ -54,6 +54,10 @@ pub enum InferenceCategoryAbbreviated {
#[serde(rename = "wf")] // NB: DO NOT CHANGE. It could break live jobs. Renamed to be fewer bytes.
#[serde(alias = "workflow")]
Workflow,
#[serde(rename = "fc")] // NB: DO NOT CHANGE. It could break live jobs. Renamed to be fewer bytes.
#[serde(alias = "format_conversion")]
FormatConversion,
}
#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
@@ -138,6 +142,7 @@ impl InferenceCategoryAbbreviated {
InferenceCategory::ImageGeneration => Self::ImageGeneration,
InferenceCategory::Mocap => Self::Mocap,
InferenceCategory::Workflow => Self::Workflow,
InferenceCategory::FormatConversion => Self::FormatConversion,
}
}
@@ -150,6 +155,7 @@ impl InferenceCategoryAbbreviated {
Self::ImageGeneration =>InferenceCategory::ImageGeneration,
Self::Mocap => InferenceCategory::Mocap,
Self::Workflow => InferenceCategory::Workflow,
Self::FormatConversion => InferenceCategory::FormatConversion,
}
}
}
@@ -250,7 +256,7 @@ mod tests {
};
let json = serde_json::ser::to_string(&args).unwrap();
assert_eq!(json, r#"{"cat":"ig","args":{"Ig":{"sd":"sd_model_token","lm":"lora_model_token","w":512,"h":512,"s":"sampler","p":"prompt","np":"n_prompt","se":1,"mu":"upload_path","cf":7,"lu":"lora_upload_path","sa":25,"bc":1,"t":"type_of_inference","de":"Option","na":"Model Name"}}}"#.to_string());
assert_eq!(json, r#"{"cat":"ig","args":{"Ig":{"sd":"sd_model_token","lm":"lora_model_token","w":512,"h":512,"s":"sampler","p":"prompt","np":"n_prompt","se":1,"mu":"upload_path","cf":7,"lu":"lora_upload_path","sa":25,"bc":1,"t":"type_of_inference","de":"Option","na":"Model Name","ve":0}}}"#.to_string());
}
#[test]
@@ -1,4 +1,5 @@
use std::collections::HashMap;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::model_weights::ModelWeightToken;
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
@@ -27,4 +28,12 @@ pub struct WorkflowArgs {
#[serde(rename = "json_modifications")]
#[serde(skip_serializing_if = "Option::is_none")]
pub maybe_json_modifications: Option<HashMap<String, NewValue>>,
#[serde(rename = "in")]
#[serde(skip_serializing_if = "Option::is_none")]
pub maybe_input_file: Option<MediaFileToken>,
#[serde(rename = "out")]
#[serde(skip_serializing_if = "Option::is_none")]
pub maybe_output_path: Option<String>,
}
@@ -88,18 +88,10 @@ pub async fn list_available_generic_inference_jobs(
)
-> AnyhowResult<Vec<AvailableInferenceJob>>
{
let model_types = args.maybe_scope_by_model_type
.map(|types| types.clone())
.unwrap_or(InferenceModelType::all_variants()); // NB: All model types
let inference_categories = args.maybe_scope_by_job_category
.map(|types| types.clone())
.unwrap_or(InferenceCategory::all_variants()); // NB: All categories
let query = if args.sort_by_priority {
list_sorted_by_priority(args, model_types, inference_categories).await
list_sorted_by_priority(args).await
} else {
list_sorted_by_id(args, model_types, inference_categories).await
list_sorted_by_id(args).await
};
let job_records = query?;
@@ -156,16 +148,43 @@ pub async fn list_available_generic_inference_jobs(
Ok(job_records)
}
async fn list_sorted_by_id(args: ListAvailableGenericInferenceJobArgs<'_>, model_types: BTreeSet<InferenceModelType>, inference_categories: BTreeSet<InferenceCategory>) -> Result<Vec<AvailableInferenceJobRawInternal>, sqlx::Error> {
async fn list_sorted_by_id(args: ListAvailableGenericInferenceJobArgs<'_>) -> Result<Vec<AvailableInferenceJobRawInternal>, sqlx::Error> {
// NB: Can't be type checked because of WHERE IN clause with dynamic contents
let mut query = core_query(&args);
// Also had to remove the following typing:
//id as `id: crate::queries::generic_inference::job::_keys::GenericInferenceJobId`,
//token AS `inference_job_token: tokens::jobs::inference::InferenceJobToken`,
//inference_type as `inference_type: enums::workers::generic_inference_type::GenericInferenceType`,
//creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,
//status as `status: crate::column_types::job_status::JobStatus`,
query.push_str(r#"
ORDER BY id ASC
LIMIT ?
"#);
let query = sqlx::query_as::<_, AvailableInferenceJobRawInternal>(&query)
.bind(args.is_debug_worker)
.bind(args.num_records);
query.fetch_all(args.mysql_pool)
.await
}
async fn list_sorted_by_priority(args: ListAvailableGenericInferenceJobArgs<'_>) -> Result<Vec<AvailableInferenceJobRawInternal>, sqlx::Error> {
// NB: Can't be type checked because of WHERE IN clause with dynamic contents
let mut query = core_query(&args);
query.push_str(r#"
ORDER BY priority_level DESC, id ASC
LIMIT ?
"#);
let query = sqlx::query_as::<_, AvailableInferenceJobRawInternal>(&query)
.bind(args.is_debug_worker)
.bind(args.num_records);
query.fetch_all(args.mysql_pool)
.await
}
// TODO(bt,2024-01-25): Make QueryBuilder
fn core_query(args: &ListAvailableGenericInferenceJobArgs<'_>) -> String {
// NB: Can't be type checked because of WHERE IN clause with dynamic contents
let mut query = r#"
SELECT
id,
@@ -207,24 +226,9 @@ SELECT
retry_at,
NOW() as database_clock
FROM generic_inference_jobs"#.to_string();
FROM generic_inference_jobs
query.push_str(&format!(r#"
WHERE
(
maybe_model_type IN ({})
)
"#, model_type_predicate(&model_types)));
query.push_str(&format!(r#"
and
(
inference_category IN ({})
)
"#, inference_category_predicate(&inference_categories)));
query.push_str(r#"
AND
WHERE
(
status IN ("pending", "attempt_failed")
)
@@ -238,111 +242,29 @@ FROM generic_inference_jobs"#.to_string();
(
is_debug_request = ?
)
ORDER BY id ASC
LIMIT ?
"#);
"#.to_string();
let query = sqlx::query_as::<_, AvailableInferenceJobRawInternal>(&query)
.bind(args.is_debug_worker)
.bind(args.num_records);
if let Some(model_types) = args.maybe_scope_by_model_type {
query.push_str(&format!(r#"
AND
(
maybe_model_type IN ({})
)
"#, model_type_predicate(model_types)));
}
query.fetch_all(args.mysql_pool)
.await
if let Some(inference_categories) = args.maybe_scope_by_job_category {
query.push_str(&format!(r#"
AND
(
inference_category IN ({})
)
"#, inference_category_predicate(&inference_categories)));
}
query
}
async fn list_sorted_by_priority(args: ListAvailableGenericInferenceJobArgs<'_>, model_types: BTreeSet<InferenceModelType>, inference_categories: BTreeSet<InferenceCategory>) -> Result<Vec<AvailableInferenceJobRawInternal>, sqlx::Error> {
// NB: Can't be type checked because of WHERE IN clause with dynamic contents
// Also had to remove the following typing:
//id as `id: crate::queries::generic_inference::job::_keys::GenericInferenceJobId`,
//token AS `inference_job_token: tokens::jobs::inference::InferenceJobToken`,
//inference_category as `inference_category: enums::workers::generic_inference_type::GenericInferenceType`,
//creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,
//status as `status: crate::column_types::job_status::JobStatus`,
let mut query = r#"
SELECT
id,
token as inference_job_token,
uuid_idempotency_token,
inference_category,
maybe_model_type,
maybe_model_token,
maybe_input_source_token,
maybe_input_source_token_type,
maybe_inference_args,
maybe_raw_inference_text,
maybe_creator_user_token,
maybe_creator_anonymous_visitor_token,
creator_ip_address,
creator_set_visibility,
status,
attempt_count,
priority_level,
is_keepalive_required,
is_from_premium_user,
is_from_api_user,
is_for_twitch,
max_duration_seconds,
is_debug_request,
maybe_routing_tag,
created_at,
updated_at,
retry_at,
NOW() as database_clock
FROM generic_inference_jobs"#.to_string();
query.push_str(&format!(r#"
WHERE
(
maybe_model_type IN ({})
)
"#, model_type_predicate(&model_types)));
query.push_str(&format!(r#"
and
(
inference_category IN ({})
)
"#, inference_category_predicate(&inference_categories)));
query.push_str(r#"
AND
(
status IN ("pending", "attempt_failed")
)
AND
(
retry_at IS NULL
OR
retry_at < CURRENT_TIMESTAMP
)
AND
(
is_debug_request = ?
)
ORDER BY priority_level DESC, id ASC
LIMIT ?
"#);
let query = sqlx::query_as::<_, AvailableInferenceJobRawInternal>(&query)
.bind(args.is_debug_worker)
.bind(args.num_records);
query.fetch_all(args.mysql_pool)
.await
}
#[derive(Debug)]
#[derive(sqlx::FromRow)]
@@ -182,6 +182,7 @@ fn raw_record_to_public_result(record: RawGenericInferenceJobStatus) -> GenericI
InferenceCategory::ImageGeneration => Some("Image Generation"),
InferenceCategory::Mocap => Some("Mocap"),
InferenceCategory::Workflow => Some("Workflow"),
InferenceCategory::FormatConversion => Some("format conversion"),
};
// NB: A bit of a hack. We store TTS results with a full path.
@@ -194,6 +195,7 @@ fn raw_record_to_public_result(record: RawGenericInferenceJobStatus) -> GenericI
InferenceCategory::ImageGeneration => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
InferenceCategory::Mocap => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
InferenceCategory::Workflow => (true, record.maybe_media_file_public_bucket_directory_hash.as_deref()),
InferenceCategory::FormatConversion => (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
@@ -3,6 +3,7 @@ use sqlx::MySqlPool;
use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory;
use enums::by_table::generic_inference_jobs::inference_input_source_token_type::InferenceInputSourceTokenType;
use enums::by_table::generic_inference_jobs::inference_job_type::InferenceJobType;
use enums::by_table::generic_inference_jobs::inference_model_type::InferenceModelType;
use enums::common::visibility::Visibility;
use errors::AnyhowResult;
@@ -15,6 +16,11 @@ use crate::payloads::generic_inference_args::generic_inference_args::GenericInfe
pub struct InsertGenericInferenceArgs<'a> {
pub uuid_idempotency_token: &'a str,
// NB: This will eventually take the place of "inference category" and "maybe model type", since the latter two are
// used entirely inconsistently for job dispatching (especially "inference category"). This should always be 1:1 with
// a concrete job type.
pub job_type: InferenceJobType,
pub inference_category: InferenceCategory,
pub maybe_model_type: Option<InferenceModelType>,
pub maybe_model_token: Option<&'a str>,
@@ -68,6 +74,8 @@ SET
token = ?,
uuid_idempotency_token = ?,
job_type = ?,
inference_category = ?,
maybe_model_type = ?,
maybe_model_token = ?,
@@ -97,6 +105,8 @@ SET
job_token.as_str(),
args.uuid_idempotency_token,
args.job_type.to_str(),
args.inference_category.to_str(),
args.maybe_model_type.map(|t| t.to_str()),
@@ -0,0 +1,38 @@
use anyhow::anyhow;
use log::error;
use sqlx::{Executor, MySql};
use sqlx::Error::Database;
use errors::AnyhowResult;
/// Insert an idempotency token (typically a UUID generated by the frontend) to make sure the
/// frontend doesn't retry the same request by mistake. There's a unique index on idempotency
/// tokens, so any retries with the same value will fail.
pub async fn insert_idempotency_token<'e, 'c, E>(
idempotency_token: &'e str,
mysql_executor: E,
) -> AnyhowResult<()>
where E: 'e + Executor<'c, Database=MySql>
{
let query = sqlx::query!(
r#"INSERT INTO idempotency_tokens SET idempotency_value = ?"#,
idempotency_token);
let query_result = query.execute(mysql_executor).await;
match query_result {
Ok(_res) => Ok(()),
Err(err) => {
error!("error inserting idempotency token: {:?}", err);
// NB: SQLSTATE[23000]: Integrity constraint violation
// NB: MySQL Error Code 1062: Duplicate key insertion (this is harder to access)
match err {
Database(ref err) => {
let _maybe_code = err.code().map(|c| c.into_owned());
},
_ => {},
}
Err(anyhow!("error inserting idempotency token: {:?}", err))
}
}
}
@@ -0,0 +1 @@
pub mod insert_idempotency_token;
@@ -1,23 +1,20 @@
use anyhow::anyhow;
use enums::common::visibility::Visibility;
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, self};
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::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::model_weights::ModelWeightToken;
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;
use crate::queries::media_files::create::insert_media_file_from_file_upload::UploadType;
// thought about this it seems like this can be a bit more geneneric instead of having this we can ...
pub struct InsertArgs<'a> {
pub pool: &'a MySqlPool,
@@ -51,13 +48,6 @@ pub struct InsertArgs<'a> {
pub maybe_public_bucket_extension: Option<&'a str>,
pub extra_file_modification_info: Option<&'a str>, // Assuming TEXT type can be represented by &str
// Creator information
pub maybe_creator_user_token: Option<&'a UserToken>,
pub maybe_creator_anonymous_visitor_token: Option<&'a AnonymousVisitorTrackingToken>,
pub creator_ip_address: &'a str,
pub creator_set_visibility: Visibility,
pub maybe_creator_file_synthetic_id_category: IdCategory,
pub maybe_creator_category_synthetic_id_category: IdCategory,
@@ -167,11 +157,11 @@ pub async fn insert_media_file_generic(
args.maybe_public_bucket_extension,
args.extra_file_modification_info,
args.maybe_creator_user_token.map(|e| e.to_string()),
args.maybe_creator_anonymous_visitor_token.map(|e| e.to_string()),
args.job.maybe_creator_user_token.as_deref(),
args.job.maybe_creator_anonymous_visitor_token.as_deref(),
args.creator_ip_address,
args.creator_set_visibility.to_str(),
args.job.creator_ip_address,
args.job.creator_set_visibility.to_str(),
maybe_creator_file_synthetic_id,
maybe_creator_category_synthetic_id,
@@ -183,9 +173,7 @@ pub async fn insert_media_file_generic(
).execute(&mut *transaction).await;
let record_id = match query_result {
Ok(res) => {
res.last_insert_id()
},
Ok(res) => res.last_insert_id(),
Err(err) => {
// TODO: handle better
//transaction.rollback().await?;
@@ -0,0 +1,98 @@
// NB: Incrementally getting rid of build warnings...
#![forbid(unused_imports)]
#![forbid(unused_mut)]
#![forbid(unused_variables)]
use anyhow::anyhow;
use sqlx::MySqlPool;
use enums::by_table::media_files::media_file_type::MediaFileType;
use enums::common::visibility::Visibility;
use errors::AnyhowResult;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::users::UserToken;
#[derive(Serialize, Debug)]
pub struct MediaFileForInference {
pub token: MediaFileToken,
pub media_type: MediaFileType,
pub maybe_creator_user_token: Option<UserToken>,
pub creator_set_visibility: Visibility,
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
}
#[derive(Serialize)]
pub struct MediaFileRaw {
pub token: MediaFileToken,
pub media_type: MediaFileType,
pub maybe_creator_user_token: Option<UserToken>,
pub creator_set_visibility: Visibility,
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
}
pub async fn get_media_file_for_inference(
media_file_token: &MediaFileToken,
mysql_pool: &MySqlPool
) -> AnyhowResult<Option<MediaFileForInference>> {
let record = select_record(media_file_token, mysql_pool).await;
let record = match record {
Ok(record) => record,
Err(ref err) => {
return match err {
sqlx::Error::RowNotFound => Ok(None),
_ => Err(anyhow!("database error: {:?}", err)),
}
}
};
Ok(Some(MediaFileForInference {
token: record.token,
media_type: record.media_type,
maybe_creator_user_token: record.maybe_creator_user_token,
creator_set_visibility: record.creator_set_visibility,
public_bucket_directory_hash: record.public_bucket_directory_hash,
maybe_public_bucket_prefix: record.maybe_public_bucket_prefix,
maybe_public_bucket_extension: record.maybe_public_bucket_extension,
}))
}
async fn select_record(
media_file_token: &MediaFileToken,
mysql_pool: &MySqlPool
) -> Result<MediaFileRaw, sqlx::Error> {
sqlx::query_as!(
MediaFileRaw,
r#"
SELECT
m.token as `token: tokens::tokens::media_files::MediaFileToken`,
m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,
m.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,
m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension
FROM media_files AS m
WHERE
m.token = ?
"#,
media_file_token
)
.fetch_one(mysql_pool)
.await
}
@@ -1,5 +1,6 @@
pub mod create;
pub mod delete_media_file;
pub mod get_media_file;
pub mod get_media_file_for_inference;
pub mod list;
pub mod update_media_file;
@@ -9,6 +9,7 @@ pub mod generic_download;
pub mod generic_inference;
pub mod generic_synthetic_ids;
pub mod health_check;
pub mod idepotency_tokens;
pub mod ip_bans;
pub mod media_files;
pub mod media_uploads;
@@ -0,0 +1,296 @@
use anyhow::anyhow;
use chrono::{DateTime, Utc};
use log::warn;
use sqlx::{Executor, FromRow, MySql, QueryBuilder, Row};
use sqlx::mysql::MySqlRow;
use enums::by_table::model_weights::weights_category::WeightsCategory;
use enums::by_table::model_weights::weights_types::WeightsType;
use enums::common::visibility::Visibility;
use enums::traits::mysql_from_row::MySqlFromRow;
use errors::AnyhowResult;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::model_weights::ModelWeightToken;
use tokens::tokens::users::UserToken;
/// This is meant to be the entire table
#[derive(Debug)]
pub struct ModelWeightForElasticsearchRecord {
pub id: i64,
pub token: ModelWeightToken,
pub creator_set_visibility: Visibility,
pub weights_type: WeightsType,
pub weights_category: WeightsCategory,
pub title: String,
// Cover images
pub maybe_cover_image_media_file_token: Option<MediaFileToken>,
pub maybe_cover_image_public_bucket_hash: Option<String>,
pub maybe_cover_image_public_bucket_prefix: Option<String>,
pub maybe_cover_image_public_bucket_extension: Option<String>,
// Creator
pub creator_user_token: UserToken,
pub creator_username: String,
pub creator_display_name: String,
pub creator_gravatar_hash: String,
// Statistics
pub maybe_ratings_positive_count: Option<u32>,
pub maybe_ratings_negative_count: Option<u32>,
pub maybe_bookmark_count: Option<u32>,
// TTS extensions
pub maybe_tts_ietf_language_tag: Option<String>,
pub maybe_tts_ietf_primary_language_subtag: Option<String>,
// VC extensions
pub maybe_voice_conversion_ietf_language_tag: Option<String>,
pub maybe_voice_conversion_ietf_primary_language_subtag: Option<String>,
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 batch_get_model_weights_for_elastic_search_backfill<'e, 'c, E>(
mysql_executor: E,
tokens: &Vec<ModelWeightToken>,
) -> AnyhowResult<Vec<ModelWeightForElasticsearchRecord>>
where E: 'e + Executor<'c, Database=MySql>
{
let maybe_models
= list_model_weights(mysql_executor, tokens)
.await;
let models : Vec<RawRecord> = match maybe_models {
Ok(models) => models,
Err(sqlx::error::Error::RowNotFound) => return Ok(Vec::new()),
Err(err) => {
warn!("model weight list query error: {:?}", err);
return Err(anyhow!("model weight list query error"));
}
};
Ok(models.into_iter()
.map(|model| {
ModelWeightForElasticsearchRecord {
id: model.id,
token: model.token,
title: model.title,
weights_type: model.weights_type,
weights_category: model.weights_category,
maybe_cover_image_media_file_token: model.maybe_cover_image_media_file_token,
maybe_cover_image_public_bucket_hash: model.maybe_cover_image_public_bucket_hash,
maybe_cover_image_public_bucket_prefix: model.maybe_cover_image_public_bucket_prefix,
maybe_cover_image_public_bucket_extension: model.maybe_cover_image_public_bucket_extension,
creator_user_token: model.creator_user_token,
creator_username: model.creator_username,
creator_display_name: model.creator_display_name,
creator_gravatar_hash: model.creator_gravatar_hash,
maybe_tts_ietf_language_tag: model.maybe_tts_ietf_language_tag,
maybe_tts_ietf_primary_language_subtag: model.maybe_tts_ietf_primary_language_subtag,
maybe_voice_conversion_ietf_language_tag: model.maybe_vc_ietf_language_tag,
maybe_voice_conversion_ietf_primary_language_subtag: model.maybe_vc_ietf_primary_language_subtag,
maybe_ratings_positive_count: model.maybe_ratings_positive_count,
maybe_ratings_negative_count: model.maybe_ratings_negative_count,
maybe_bookmark_count: model.maybe_bookmark_count,
creator_set_visibility: model.creator_set_visibility,
created_at: model.created_at,
updated_at: model.updated_at,
user_deleted_at: model.user_deleted_at,
mod_deleted_at: model.mod_deleted_at,
}
})
.collect::<Vec<ModelWeightForElasticsearchRecord>>())
}
async fn list_model_weights<'e, 'c, E>(
mysql_executor: E,
tokens: &Vec<ModelWeightToken>,
) -> Result<Vec<RawRecord>, sqlx::Error>
where E: 'e + Executor<'c, Database=MySql>
{
if tokens.len() == 0 {
return Ok(Vec::new());
}
let mut query_builder: QueryBuilder<MySql> = QueryBuilder::new(
r#"
SELECT
w.id,
w.token,
w.creator_set_visibility,
w.weights_type,
w.weights_category,
w.title,
cover_image.token as maybe_cover_image_media_file_token,
cover_image.public_bucket_directory_hash as maybe_cover_image_public_bucket_hash,
cover_image.maybe_public_bucket_prefix as maybe_cover_image_public_bucket_prefix,
cover_image.maybe_public_bucket_extension as maybe_cover_image_public_bucket_extension,
w.creator_user_token,
users.username as creator_username,
users.display_name as creator_display_name,
users.email_gravatar_hash as creator_gravatar_hash,
entity_stats.ratings_positive_count as maybe_ratings_positive_count,
entity_stats.ratings_negative_count as maybe_ratings_negative_count,
entity_stats.bookmark_count as maybe_bookmark_count,
extension_tts.ietf_language_tag as maybe_tts_ietf_language_tag,
extension_tts.ietf_primary_language_subtag as maybe_tts_ietf_primary_language_subtag,
extension_vc.ietf_language_tag as maybe_vc_ietf_language_tag,
extension_vc.ietf_primary_language_subtag as maybe_vc_ietf_primary_language_subtag,
w.created_at,
w.updated_at,
w.user_deleted_at,
w.mod_deleted_at
FROM model_weights as w
JOIN users
ON users.token = w.creator_user_token
LEFT OUTER JOIN model_weights_extension_tts_details as extension_tts
ON extension_tts.model_weights_token = w.token
LEFT OUTER JOIN model_weights_extension_voice_conversion_details as extension_vc
ON extension_vc.model_weights_token = w.token
LEFT OUTER JOIN entity_stats
ON entity_stats.entity_type = "model_weights"
AND entity_stats.entity_token = w.token
LEFT OUTER JOIN media_files as cover_image
ON cover_image.token = w.maybe_cover_image_media_file_token
WHERE w.token IN (
"#);
// NB: Syntax will be wrong if list has zero length
let mut separated = query_builder.separated(", ");
for token in tokens.iter() {
separated.push_bind(token.as_str());
}
separated.push_unseparated(") ");
// NB: We should return the most recent update timestamp last (for cursoring)
query_builder.push(" ORDER BY w.updated_at ASC, w.id ASC ");
let query = query_builder.build_query_as::<RawRecord>();
let maybe_results = query.fetch_all(mysql_executor).await?;
Ok(maybe_results)
}
struct RawRecord {
pub id: i64,
pub token: ModelWeightToken,
pub creator_set_visibility: Visibility,
pub weights_type: WeightsType,
pub weights_category: WeightsCategory,
pub title: String,
// Cover images
pub maybe_cover_image_media_file_token: Option<MediaFileToken>,
pub maybe_cover_image_public_bucket_hash: Option<String>,
pub maybe_cover_image_public_bucket_prefix: Option<String>,
pub maybe_cover_image_public_bucket_extension: Option<String>,
// Creator
pub creator_user_token: UserToken,
pub creator_username: String,
pub creator_display_name: String,
pub creator_gravatar_hash: String,
// Statistics
pub maybe_ratings_positive_count: Option<u32>,
pub maybe_ratings_negative_count: Option<u32>,
pub maybe_bookmark_count: Option<u32>,
// TTS extensions
pub maybe_tts_ietf_language_tag: Option<String>,
pub maybe_tts_ietf_primary_language_subtag: Option<String>,
// Voice conversion extensions
pub maybe_vc_ietf_language_tag: Option<String>,
pub maybe_vc_ietf_primary_language_subtag: Option<String>,
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 RawRecord {
fn from_row(row: &MySqlRow) -> Result<Self, sqlx::Error> {
// NB(bt): Not sure why IDE is complaining about inlining this with the call to map() below.
let maybe_cover_image_media_file_token: Option<String> = row.try_get("maybe_cover_image_media_file_token")?;
Ok(Self {
id: row.try_get("id")?,
token: ModelWeightToken::new(row.try_get("token")?),
creator_set_visibility: Visibility::try_from_mysql_row(row, "creator_set_visibility")?,
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_cover_image_media_file_token: maybe_cover_image_media_file_token.map(|token| MediaFileToken::new(token)),
maybe_cover_image_public_bucket_hash: row.try_get("maybe_cover_image_public_bucket_hash")?,
maybe_cover_image_public_bucket_prefix: row.try_get("maybe_cover_image_public_bucket_prefix")?,
maybe_cover_image_public_bucket_extension: row.try_get("maybe_cover_image_public_bucket_extension")?,
creator_user_token: UserToken::new(row.try_get("creator_user_token")?),
creator_username: row.try_get("creator_username")?,
creator_display_name: row.try_get("creator_display_name")?,
creator_gravatar_hash: row.try_get("creator_gravatar_hash")?,
maybe_ratings_positive_count: row.try_get("maybe_ratings_positive_count")?,
maybe_ratings_negative_count: row.try_get("maybe_ratings_negative_count")?,
maybe_bookmark_count: row.try_get("maybe_bookmark_count")?,
maybe_tts_ietf_language_tag: row.try_get("maybe_tts_ietf_language_tag")?,
maybe_tts_ietf_primary_language_subtag: row.try_get("maybe_tts_ietf_primary_language_subtag")?,
maybe_vc_ietf_language_tag: row.try_get("maybe_vc_ietf_language_tag")?,
maybe_vc_ietf_primary_language_subtag: row.try_get("maybe_vc_ietf_primary_language_subtag")?,
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")?,
})
}
}
@@ -0,0 +1 @@
pub mod batch_get_model_weights_for_elastic_search_backfill;
@@ -0,0 +1,55 @@
use chrono::{DateTime, Utc};
use sqlx::{Executor, MySql, Row};
use enums::by_table::model_weights::weights_types::WeightsType;
use errors::AnyhowResult;
use tokens::tokens::model_weights::ModelWeightToken;
pub struct UpdatedModelWeight {
pub token: ModelWeightToken,
pub weights_type: WeightsType,
pub updated_at: DateTime<Utc>,
}
/// Get records updated since a particular date.
/// NB: This doesn't batch yet at our scale, but in the future we'll likely need/want to batch,
/// and then we'll need another way to cursor in the event that batch_size < # records updated
/// within a given time quantum.
pub async fn list_model_weight_tokens_updated_since<'e, 'c, E>(
mysql_executor: E,
since_date: &DateTime<Utc>,
) -> AnyhowResult<Vec<UpdatedModelWeight>>
where E: 'e + Executor<'c, Database=MySql>
{
let query = sqlx::query_as!(
UpdatedModelWeightRaw,
r#"
SELECT
token as `token: tokens::tokens::model_weights::ModelWeightToken`,
weights_type as `weights_type: enums::by_table::model_weights::weights_types::WeightsType`,
updated_at
FROM model_weights
WHERE
updated_at > ?
"#,
since_date
);
let results = query.fetch_all(mysql_executor).await?;
let results = results.into_iter()
.map(|record| UpdatedModelWeight {
token: record.token,
weights_type: record.weights_type,
updated_at: record.updated_at,
})
.collect();
Ok(results)
}
pub struct UpdatedModelWeightRaw {
pub token: ModelWeightToken,
pub weights_type: WeightsType,
pub updated_at: DateTime<Utc>,
}
@@ -1,6 +1,7 @@
pub mod list_model_weight_tokens_updated_since;
pub mod list_model_weights_for_elastic_search_backfill_using_cursor;
pub mod list_model_weights_for_text_to_speech;
pub mod list_model_weights_for_voice_conversion;
pub mod list_weights_by_tokens;
pub mod list_weights_by_user;
pub mod list_weights_query_builder;
pub mod list_weights_query_builder;
@@ -1,3 +1,4 @@
pub mod batch_get;
pub mod create;
pub mod delete_weights;
pub mod edit;
@@ -149,7 +149,11 @@ impl RawUserBookmarkRecord {
MediaFileType::Audio => Some("audio media file".to_string()),
MediaFileType::Image => Some("image media file".to_string()),
MediaFileType::Video => Some("video media file".to_string()),
MediaFileType::Mocap => Some("mocap da file".to_string()),
MediaFileType::Mocap => Some("mocap file".to_string()),
MediaFileType::Bvh => Some("3d file".to_string()),
MediaFileType::Fbx => Some("3d file".to_string()),
MediaFileType::Glb => Some("3d file".to_string()),
MediaFileType::Gltf => Some("3d file".to_string()),
}),
UserBookmarkEntityType::VoiceConversionModel => self.maybe_descriptive_text_voice_conversion_model_title,
UserBookmarkEntityType::ZsVoice => self.maybe_descriptive_text_zs_voice_title,
+11 -1
View File
@@ -14,11 +14,21 @@ path = "src/lib.rs"
[dependencies]
# External
# Internal
errors = { path = "../errors" }
filesys = { path = "../files/filesys" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
# External
log = "0.4.14"
subprocess = "0.2.9"
# Serialization
#serde = "1.0.125"
#serde_derive = "1.0.125"
#serde_json = "1.0.64"
#toml = "0.5.8"
[dev-dependencies]
serial_test = "3.0.0"
@@ -46,7 +46,7 @@ impl CommandExitStatus {
mod tests {
use subprocess::ExitStatus;
use crate::job::job_loop::command_exit_status::CommandExitStatus;
use crate::command_exit_status::CommandExitStatus;
#[test]
fn is_success() {
@@ -0,0 +1,7 @@
/// Trait for arguments that can be turned into a command line string.
pub trait CommandArgs {
/// Convert to the call signature.
/// NB: We're not returning a HashMap since positions may need to be respected, or some args may have no values.
fn to_command_string(&self) -> String;
}
@@ -0,0 +1,291 @@
use std::env;
use std::ffi::OsString;
use std::fs::File;
use std::path::PathBuf;
use std::time::Duration;
use log::info;
use subprocess::{Popen, PopenConfig, Redirection};
use errors::AnyhowResult;
use filesys::path_to_string::path_to_string;
use crate::command_exit_status::CommandExitStatus;
use crate::command_runner::command_args::CommandArgs;
use crate::command_runner::command_runner_args::{FileOrCreate, RunAsSubprocessArgs};
use crate::command_runner::env_var_policy::EnvVarPolicy;
use crate::docker_options::DockerOptions;
use crate::executable_or_command::ExecutableOrShellCommand;
#[derive(Clone)]
pub struct CommandRunner {
/// The script, executable, or command to run.
pub executable_or_command: ExecutableOrShellCommand,
/// The directory to change to before running the command.
/// The parent process won't change directory here - only the subprocess will.
pub maybe_execution_directory: Option<PathBuf>,
/// How to treat the env vars when spinning up the subprocess.
pub env_var_policy: EnvVarPolicy,
/// If the command needs to run with a Python virtual env, this can be run.
/// eg. `source python/bin/activate`
pub maybe_virtual_env_activation_command: Option<String>,
/// If this is run under Docker (eg. in development), these are the options.
/// Typically, this is not done in production.
pub maybe_docker_options: Option<DockerOptions>,
/// If the execution should be ended after a certain point.
pub maybe_execution_timeout: Option<Duration>,
}
impl CommandRunner {
/// Run the command with the `subprocess` crate utilities
pub fn run_with_subprocess(&self, args: RunAsSubprocessArgs<'_>) -> CommandExitStatus {
self.do_run_with_subprocess(args)
.unwrap_or_else(|err| {
CommandExitStatus::FailureWithReason { reason: format!("error: {:?}", err) }
})
}
fn do_run_with_subprocess(&self, args: RunAsSubprocessArgs<'_>) -> AnyhowResult<CommandExitStatus> {
let command = self.build_command_string(&args.args);
info!("Command: {:?}", command);
let shell = match self.executable_or_command {
ExecutableOrShellCommand::BashShellCommand(_) | ExecutableOrShellCommand::Executable(_) => "bash",
ExecutableOrShellCommand::ShShellCommand(_) => "sh",
};
let command_parts = [
shell,
"-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() {
// TODO(bt,2024-01-27): Env var sanitization
//if IGNORED_ENVIRONMENT_VARS.contains(&env_key) {
// continue;
//}
env_vars.push((
OsString::from(env_key),
OsString::from(env_value),
));
}
let mut config = PopenConfig::default();
if !env_vars.is_empty() {
config.env = Some(env_vars);
}
match args.maybe_stderr_output_file {
Some(FileOrCreate::NewFileWithName(stderr_output_file)) => {
info!("stderr will be written to file: {:?}", stderr_output_file);
let stderr_file = File::create(stderr_output_file)?;
config.stderr = Redirection::File(stderr_file);
},
_ => {},
}
match args.maybe_stdout_output_file {
Some(FileOrCreate::NewFileWithName(stdout_output_file)) => {
info!("stdout will be written to file: {:?}", stdout_output_file);
let stdout_file = File::create(stdout_output_file)?;
config.stdout = Redirection::File(stdout_file);
},
_ => {},
}
let mut popen_handle = Popen::create(&command_parts, config)?;
info!("Subprocess PID: {:?}", popen_handle.pid());
let maybe_exit_status = match self.maybe_execution_timeout {
None => {
let exit_status = popen_handle.wait()?;
info!("Subprocess exit status: {:?}", exit_status);
return Ok(CommandExitStatus::from_exit_status(exit_status));
}
Some(timeout) => {
info!("Executing with timeout: {:?}", &timeout);
popen_handle.wait_timeout(timeout.clone())?
}
};
match maybe_exit_status {
None => {
// NB: If the program didn't successfully terminate, kill it.
info!("Subprocess didn't end after timeout; terminating...");
let _r = popen_handle.terminate()?;
Ok(CommandExitStatus::Timeout)
}
Some(exit_status) => {
info!("Subprocess timed wait exit status: {:?}", exit_status);
Ok(CommandExitStatus::from_exit_status(exit_status))
}
}
}
fn build_command_string(&self, command_args: &Box<&dyn CommandArgs>) -> String {
let mut command = String::new();
if let Some(execution_directory) = self.maybe_execution_directory.as_deref() {
command.push_str("cd ");
command.push_str(&path_to_string(execution_directory));
command.push_str(" && ");
}
if let Some(venv_command) = self.maybe_virtual_env_activation_command.as_deref() {
command.push_str(venv_command);
command.push_str(" && ");
}
match self.executable_or_command {
ExecutableOrShellCommand::Executable(ref executable) => {
command.push_str(&path_to_string(executable));
command.push_str(" ");
}
ExecutableOrShellCommand::ShShellCommand(ref cmd) => {
command.push_str(cmd);
command.push_str(" ");
}
ExecutableOrShellCommand::BashShellCommand(ref cmd) => {
command.push_str(cmd);
command.push_str(" ");
}
}
let command_arguments = command_args.to_command_string();
command.push_str(&command_arguments);
if let Some(docker_options) = self.maybe_docker_options.as_ref() {
command = docker_options.to_command_string(&command);
}
command
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use crate::command_runner::command_args::CommandArgs;
use crate::command_runner::command_runner::CommandRunner;
use crate::command_runner::env_var_policy::EnvVarPolicy;
use crate::docker_options::{DockerGpu, DockerOptions};
use crate::executable_or_command::ExecutableOrShellCommand;
struct BlenderArgs;
impl CommandArgs for BlenderArgs {
fn to_command_string(&self) -> String {
"-a --foo --bar=baz --bin=\"blah blah\"".to_string()
}
}
#[test]
fn simple_executable() {
let runner = CommandRunner {
executable_or_command: ExecutableOrShellCommand::Executable(PathBuf::from("blender")),
maybe_execution_directory: None,
env_var_policy: EnvVarPolicy::CopyNone,
maybe_virtual_env_activation_command: None,
maybe_docker_options: None,
maybe_execution_timeout: None,
};
let ptr : Box<&dyn CommandArgs> = Box::new(&BlenderArgs {});
let result = runner.build_command_string(&ptr);
assert_eq!(&result, r#"blender -a --foo --bar=baz --bin="blah blah""#);
}
#[test]
fn simple_command() {
let runner = CommandRunner {
executable_or_command: ExecutableOrShellCommand::BashShellCommand("python3.6 inference.py".to_string()),
maybe_execution_directory: None,
env_var_policy: EnvVarPolicy::CopyNone,
maybe_virtual_env_activation_command: None,
maybe_docker_options: None,
maybe_execution_timeout: None,
};
let ptr : Box<&dyn CommandArgs> = Box::new(&BlenderArgs {});
let result = runner.build_command_string(&ptr);
assert_eq!(&result, r#"python3.6 inference.py -a --foo --bar=baz --bin="blah blah""#);
}
#[test]
fn executable_with_directory() {
let runner = CommandRunner {
executable_or_command: ExecutableOrShellCommand::Executable(PathBuf::from("blender")),
maybe_execution_directory: Some(PathBuf::from("/usr/local/bin")),
env_var_policy: EnvVarPolicy::CopyNone,
maybe_virtual_env_activation_command: None,
maybe_docker_options: None,
maybe_execution_timeout: None,
};
let ptr : Box<&dyn CommandArgs> = Box::new(&BlenderArgs {});
let result = runner.build_command_string(&ptr);
assert_eq!(&result, r#"cd /usr/local/bin && blender -a --foo --bar=baz --bin="blah blah""#);
}
#[test]
fn command_with_venv() {
let runner = CommandRunner {
executable_or_command: ExecutableOrShellCommand::BashShellCommand("python3.6 inference.py".to_string()),
maybe_execution_directory: None,
env_var_policy: EnvVarPolicy::CopyNone,
maybe_virtual_env_activation_command: Some("source venv/bin/activate".to_string()),
maybe_docker_options: None,
maybe_execution_timeout: None,
};
let ptr : Box<&dyn CommandArgs> = Box::new(&BlenderArgs {});
let result = runner.build_command_string(&ptr);
assert_eq!(&result, r#"source venv/bin/activate && python3.6 inference.py -a --foo --bar=baz --bin="blah blah""#);
}
// TODO(bt, 2024-01-27): The quotes are broken. Fix this!!
#[test]
fn execute_with_docker() {
let runner = CommandRunner {
executable_or_command: ExecutableOrShellCommand::Executable(PathBuf::from("blender")),
maybe_execution_directory: None,
env_var_policy: EnvVarPolicy::CopyNone,
maybe_virtual_env_activation_command: None,
maybe_docker_options: Some(DockerOptions {
image_name: "ABCDE".to_string(),
maybe_bind_mount: None,
maybe_environment_variables: None,
maybe_gpu: Some(DockerGpu::All),
}),
maybe_execution_timeout: None,
};
let ptr : Box<&dyn CommandArgs> = Box::new(&BlenderArgs {});
let result = runner.build_command_string(&ptr);
// TODO(bt, 2024-01-27): The quotes are broken. Fix this!!
assert_eq!(&result, r#"docker run --rm --gpus all ABCDE /bin/bash -c "blender -a --foo --bar=baz --bin="blah blah"""#);
}
}
@@ -0,0 +1,27 @@
use std::path::Path;
use crate::command_runner::command_args::CommandArgs;
#[derive(Clone, Debug)]
pub enum FileOrCreate<'a> {
/// Open the file at the path. Overwrite if it already exists.
NewFileWithName(&'a Path),
// Future option(1): new file
// /// Create a new file (and presumably return the name)
// NewFile,
// Future option(2): append-only file
// Future option(3): already open file handle
}
pub struct RunAsSubprocessArgs<'a> {
/// The args for the process we're going to call
pub args: Box<&'a dyn CommandArgs>,
/// If set, write stderr to this file.
pub maybe_stderr_output_file: Option<FileOrCreate<'a>>,
/// If set, write stdout to this file.
pub maybe_stdout_output_file: Option<FileOrCreate<'a>>,
}
@@ -0,0 +1,138 @@
use std::collections::HashSet;
use std::env;
use std::ffi::OsString;
#[derive(Clone, Debug)]
pub enum EnvVarPolicy {
/// Copy no env vars to the subprocess
CopyNone,
/// Copy all env vars to the subprocess
CopyAll,
/// Copy only the following env vars to the subprocess
CopyOnly(HashSet<String>),
/// Copy all except the following env vars to the subprocess
CopyExcept(HashSet<String>),
}
impl EnvVarPolicy {
/// Build the list of env vars to send to the subprocess, if any, based on the policy.
pub fn get_env_vars_for_subprocess(&self) -> Option<Vec<(OsString, OsString)>> {
match self {
EnvVarPolicy::CopyNone => {
None
}
EnvVarPolicy::CopyAll => {
Some(env::vars().into_iter()
.map(|(key, value)| (OsString::from(key), OsString::from(value)))
.collect::<Vec<_>>())
}
EnvVarPolicy::CopyOnly(names) => {
Some(env::vars().into_iter()
.filter(|(key, _value)| names.contains(key))
.map(|(key, value)| (OsString::from(key), OsString::from(value)))
.collect::<Vec<_>>())
}
EnvVarPolicy::CopyExcept(names) => {
Some(env::vars().into_iter()
.filter(|(key, _value)| !names.contains(key))
.map(|(key, value)| (OsString::from(key), OsString::from(value)))
.collect::<Vec<_>>())
}
}
}
}
#[cfg(test)]
mod tests {
use std::collections::HashSet;
use crate::command_runner::env_var_policy::EnvVarPolicy;
// NB: Careful with `set_var` as it escapes into *all* other tests! To make matters
// more complicated, tests run in parallel and in any order. We're using a special
// test variable so we never infect other tests.
fn set_env_vars() {
std::env::set_var("STORYTELLER_TEST_VAR_FOO", "foo");
std::env::set_var("STORYTELLER_TEST_VAR_BAR", "bar");
std::env::set_var("STORYTELLER_TEST_VAR_BAZ", "baz");
std::env::set_var("STORYTELLER_TEST_VAR_BIN", "bin");
}
// NB: Careful with `set_var` as it escapes into *all* other tests! To make matters
// more complicated, tests run in parallel and in any order. We're using a special
// test variable so we never infect other tests.
fn unset_env_vars() {
std::env::remove_var("STORYTELLER_TEST_VAR_FOO");
std::env::remove_var("STORYTELLER_TEST_VAR_BAR");
std::env::remove_var("STORYTELLER_TEST_VAR_BAZ");
std::env::remove_var("STORYTELLER_TEST_VAR_BIN");
}
#[test]
#[serial_test::serial]
fn copy_none() {
let policy = EnvVarPolicy::CopyNone;
assert_eq!(policy.get_env_vars_for_subprocess(), None);
}
#[test]
#[serial_test::serial]
fn copy_all() {
let policy = EnvVarPolicy::CopyAll;
set_env_vars();
let args = policy.get_env_vars_for_subprocess().expect("should contain args");
// NB: All variables contained
assert!(args.iter().find(|(key, _value)| key == "STORYTELLER_TEST_VAR_FOO").is_some());
assert!(args.iter().find(|(key, _value)| key == "STORYTELLER_TEST_VAR_BAR").is_some());
assert!(args.iter().find(|(key, _value)| key == "STORYTELLER_TEST_VAR_BAZ").is_some());
assert!(args.iter().find(|(key, _value)| key == "STORYTELLER_TEST_VAR_BIN").is_some());
unset_env_vars();
}
#[test]
#[serial_test::serial]
fn copy_except() {
let policy = EnvVarPolicy::CopyExcept(HashSet::from([
"STORYTELLER_TEST_VAR_FOO".to_string(),
"STORYTELLER_TEST_VAR_BIN".to_string(),
]));
set_env_vars();
let args = policy.get_env_vars_for_subprocess().expect("should contain args");
// NB: Only two contained
assert!(args.iter().find(|(key, _value)| key == "STORYTELLER_TEST_VAR_FOO").is_none());
assert!(args.iter().find(|(key, _value)| key == "STORYTELLER_TEST_VAR_BAR").is_some());
assert!(args.iter().find(|(key, _value)| key == "STORYTELLER_TEST_VAR_BAZ").is_some());
assert!(args.iter().find(|(key, _value)| key == "STORYTELLER_TEST_VAR_BIN").is_none());
unset_env_vars();
}
#[test]
#[serial_test::serial]
fn copy_only() {
let policy = EnvVarPolicy::CopyOnly(HashSet::from([
"STORYTELLER_TEST_VAR_FOO".to_string(),
"STORYTELLER_TEST_VAR_BIN".to_string(),
]));
set_env_vars();
let args = policy.get_env_vars_for_subprocess().expect("should contain args");
// NB: Only two contained
assert!(args.iter().find(|(key, _value)| key == "STORYTELLER_TEST_VAR_FOO").is_some());
assert!(args.iter().find(|(key, _value)| key == "STORYTELLER_TEST_VAR_BAR").is_none());
assert!(args.iter().find(|(key, _value)| key == "STORYTELLER_TEST_VAR_BAZ").is_none());
assert!(args.iter().find(|(key, _value)| key == "STORYTELLER_TEST_VAR_BIN").is_some());
unset_env_vars();
}
}
@@ -0,0 +1,4 @@
pub mod command_args;
pub mod command_runner;
pub mod command_runner_args;
pub mod env_var_policy;
@@ -0,0 +1,17 @@
use std::path::PathBuf;
/// A single executable script or a string to run as a shell command.
#[derive(Clone)]
pub enum ExecutableOrShellCommand {
/// A system path to an executable.
/// Eg. `inference.py`
Executable(PathBuf),
/// A string to run as `sh` shell command.
/// Eg. `python3 inference.py`
ShShellCommand(String),
/// A string to run as a `bash` shell command.
/// Eg. `python3 inference.py`
BashShellCommand(String),
}
+4 -1
View File
@@ -13,4 +13,7 @@
#![allow(dead_code)]
#![allow(non_snake_case)]
pub mod docker_options;
pub mod command_runner;
pub mod command_exit_status;
pub mod docker_options;
pub mod executable_or_command;
@@ -0,0 +1,66 @@
[package]
name = "es-update-job"
publish = false
edition = "2018"
version = "0.0.1"
authors = [
"Brandon Thomas <bt@brand.io>",
"Brandon Thomas <echelon@gmail.com>",
]
[[bin]]
name = "es-update-job"
path = "src/main.rs"
[dependencies]
# Internal
actix_helpers = { path = "../../../lib/actix_helpers" }
bootstrap = { path = "../../../lib/bootstrap" }
concurrency = { path = "../../../lib/concurrency" }
config = { path = "../../../lib/deprecated/config" }
container_common = { path = "../../../lib/deprecated/container_common" }
easyenv = { path = "../../../lib/easyenv" }
elasticsearch_schema = { path = "../../../lib/elasticsearch_schema" }
enums = { path = "../../../_types/enums" }
errors = { path = "../../../lib/errors" }
filesys = { path = "../../../lib/files/filesys" }
hashing = { path = "../../../lib/files/hashing" }
jobs_common = { path = "../../../lib/jobs_common" }
media = { path = "../../../lib/files/media" }
memory_caching = { path = "../../../lib/caching/memory_caching" }
mimetypes = { path = "../../../lib/files/mimetypes" }
mysql_queries = { path = "../../../lib/mysql_queries" }
redis_common = { path = "../../../lib/redis_common" }
reusable_types = { path = "../../../_types/reusable_types" }
server_environment = { path = "../../../lib/server_environment" }
storyteller_root = { path = "../../../lib/storyteller_root" }
subprocess_common = { path = "../../../lib/subprocess_common" }
testing = { path = "../../../lib/testing" }
tokens = { path = "../../../_types/tokens" }
# 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"] }
dotenv = "0.15.0"
hostname = "0.3.1"
log = "0.4.14"
once_cell = "1.18.0"
rand = "0.8.5"
subprocess = "0.2.9"
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
elasticsearch = "8.5.0-alpha.1"
# Runtime
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
tokio = { version = "1.34.0", features = ["macros"] }
# Serialization
serde = "1.0.125"
serde_derive = "1.0.125"
serde_json = "1.0.108"
@@ -0,0 +1,2 @@
# es-update-job default .env configurations (these can be overridden with k8s)
@@ -0,0 +1,4 @@
# es-update-job default .env configurations (these can be overridden with k8s)
MYSQL_URL=mysql://storyteller:password@localhost/storyteller
ELASTICSEARCH_URL=http://localhost:9200
@@ -0,0 +1 @@
# es-update-job default .env configurations (these can be overridden with k8s)
@@ -0,0 +1,27 @@
use elasticsearch::Elasticsearch;
use sqlx::MySqlPool;
pub struct JobState {
pub mysql_pool: MySqlPool,
pub elasticsearch: Elasticsearch,
pub sleep_config: SleepConfigs,
}
/// Use sleep to not overload the database.
pub struct SleepConfigs {
// How long to wait between individual ES writes.
pub between_es_writes_wait_millis: u64,
// How long to wait between individual job batches.
pub between_job_batch_wait_millis: u64,
// How long to wait between individual queries.
pub between_query_wait_millis: u64,
// How long to wait between errors.
pub between_error_wait_millis: u64,
// How long to wait between periods of NO-OPs / no updates.
pub between_no_updates_wait_millis: u64,
}
@@ -0,0 +1,90 @@
// Never allow these
#![forbid(private_bounds)]
#![forbid(private_interfaces)]
#![forbid(unused_must_use)] // NB: It's unsafe to not close/check some things
// Okay to toggle
#![forbid(unreachable_patterns)]
#![forbid(unused_imports)]
#![forbid(unused_mut)]
#![forbid(unused_variables)]
// Always allow
#![allow(dead_code)]
#![allow(non_snake_case)]
// Strict
//#![forbid(warnings)]
use elasticsearch::Elasticsearch;
use elasticsearch::http::transport::Transport;
use log::info;
use sqlx::{MySql, Pool};
use sqlx::mysql::MySqlPoolOptions;
use bootstrap::bootstrap::{bootstrap, BootstrapArgs};
use config::shared_constants::DEFAULT_MYSQL_CONNECTION_STRING;
use config::shared_constants::DEFAULT_RUST_LOG;
use errors::AnyhowResult;
use crate::job_state::{JobState, SleepConfigs};
use crate::main_loop::main_loop;
pub mod job_state;
pub mod main_loop;
#[tokio::main]
async fn main() -> AnyhowResult<()> {
let container_environment = bootstrap(BootstrapArgs {
app_name: "es-update-job",
default_logging_override: Some(DEFAULT_RUST_LOG),
config_search_directories: &[".", "./config", "crates/service/job/es-update-job/config"],
})?;
info!("Hostname: {}", &container_environment.hostname);
let mysql_pool = get_mysql_pool().await?;
let elasticsearch = get_elasticsearch_client()?;
let job_state = JobState {
mysql_pool,
elasticsearch,
sleep_config: SleepConfigs {
between_es_writes_wait_millis: easyenv::get_env_num("BETWEEN_WRITES_WAIT_MILLIS", 100)?,
between_job_batch_wait_millis: easyenv::get_env_num("BETWEEN_JOB_BATCH_WAIT_MILLIS", 500)?,
between_query_wait_millis: easyenv::get_env_num("BETWEEN_QUERY_WAIT_MILLIS", 100)?,
between_error_wait_millis: easyenv::get_env_num("BETWEEN_ERROR_WAIT_MILLIS", 10_000)?,
between_no_updates_wait_millis: easyenv::get_env_num("BETWEEN_NO_UPDATES_WAIT_MILLIS", 20_000)?,
},
};
let _r = main_loop(job_state).await;
Ok(())
}
async fn get_mysql_pool() -> AnyhowResult<Pool<MySql>> {
info!("Connecting to MySQL database...");
let db_connection_string =
easyenv::get_env_string_or_default(
"MYSQL_URL",
DEFAULT_MYSQL_CONNECTION_STRING);
let mysql_pool = MySqlPoolOptions::new()
.max_connections(5)
.connect(&db_connection_string)
.await?;
Ok(mysql_pool)
}
fn get_elasticsearch_client() -> AnyhowResult<Elasticsearch> {
info!("Connecting to Elasticsearch...");
let transport = Transport::single_node(&easyenv::get_env_string_required("ELASTICSEARCH_URL")?)?;
// TODO(bt,2023-10-26): Allow connecting to instances by URL instead of the default dev URL.
Ok(Elasticsearch::new(transport))
}
@@ -0,0 +1,179 @@
use std::time::Duration;
use anyhow::anyhow;
use chrono::{DateTime, Utc};
use elasticsearch::{BulkOperation, BulkParts, Elasticsearch};
use log::{error, info};
use serde_json::Value;
use elasticsearch_schema::documents::model_weight_document::{MODEL_WEIGHT_INDEX, ModelWeightDocument};
use elasticsearch_schema::traits::document::Document;
use enums::by_table::model_weights::weights_category::WeightsCategory;
use errors::AnyhowResult;
use mysql_queries::queries::model_weights::batch_get::batch_get_model_weights_for_elastic_search_backfill::{batch_get_model_weights_for_elastic_search_backfill, ModelWeightForElasticsearchRecord};
use mysql_queries::queries::model_weights::list::list_model_weight_tokens_updated_since::list_model_weight_tokens_updated_since;
use crate::job_state::JobState;
pub async fn main_loop(job_state: JobState) {
// TODO(bt,2024-02-05): Write this cursor to Redis so job can resume without reindexing everything.
let mut cursor = DateTime::UNIX_EPOCH;
loop {
info!("Main loop; cursor @ {:?}", &cursor);
let result = with_database_main_loop(&mut cursor, &job_state).await;
if let Err(err) = result {
error!("Error in main loop: {:?}", err);
std::thread::sleep(Duration::from_millis(job_state.sleep_config.between_error_wait_millis));
}
// NB: Function should never return in success case, but we'll sleep just in case.
}
}
pub async fn with_database_main_loop(updated_at_cursor: &mut DateTime<Utc>, job_state: &JobState) -> AnyhowResult<()> {
info!("Acquiring MySQL connection...");
let mut mysql_connection = job_state.mysql_pool.acquire().await?;
let mut last_successful_update_at = updated_at_cursor.clone();
loop {
info!("Querying tokens updated since: {:?}", &updated_at_cursor);
let mut maybe_tokens =
list_model_weight_tokens_updated_since(&mut *mysql_connection, &updated_at_cursor).await?;
if maybe_tokens.is_empty() {
info!("No records updated since {:?}", &updated_at_cursor);
std::thread::sleep(Duration::from_millis(job_state.sleep_config.between_no_updates_wait_millis));
continue;
}
info!("Found {} updated records", maybe_tokens.len());
let mut last_observed_updated_at = updated_at_cursor.clone();
while !maybe_tokens.is_empty() {
// NB: This list might be very large if we query from (1) the epoch, or (2) there was a large series of updates
let last = 50.min(maybe_tokens.len());
let drained_tokens = maybe_tokens.drain(0..last)
.into_iter()
.map(|record| record.token)
.collect::<Vec<_>>();
let records
= batch_get_model_weights_for_elastic_search_backfill(&mut *mysql_connection, &drained_tokens).await?;
for record in records {
let updated_at = record.updated_at.clone();
create_document_from_record(&job_state.elasticsearch, record).await?;
// NB: We don't want to advance the cursor to the current second just yet, because we might be processing
// several records with the exact same "updated_at" timestamp. We do know that due to ordering, we can update
// the timestamp cursor to the second before this timestamp, however.
//
// If we change the cursoring or batch sizes, we'll need to reconsider all of this logic.
if let Some(cursor_at_least) = updated_at.checked_sub_signed(chrono::Duration::seconds(1)) {
// NB: We don't want the cursor to slide backwards (it shouldn't, but the max will save us).
last_successful_update_at = cursor_at_least.max(last_successful_update_at);
}
*updated_at_cursor = last_successful_update_at.max(*updated_at_cursor);
last_observed_updated_at = updated_at;
std::thread::sleep(Duration::from_millis(job_state.sleep_config.between_es_writes_wait_millis));
}
std::thread::sleep(Duration::from_millis(job_state.sleep_config.between_job_batch_wait_millis));
}
// NB: The last cursor math put us a second behind the current clock. Here we'll advance to the last record.
// NB: This technically could miss records if we're updating within the same second batch we read from, but that
// seems both unlikely and not worth solving at our present scale.
*updated_at_cursor = last_observed_updated_at.max(*updated_at_cursor);
info!("Up to date at cursor = {:?}", &updated_at_cursor);
std::thread::sleep(Duration::from_millis(job_state.sleep_config.between_query_wait_millis));
}
}
async fn create_document_from_record(elasticsearch: &Elasticsearch, record: ModelWeightForElasticsearchRecord) -> AnyhowResult<()> {
info!("Create record for {:?} - {:?}", record.token, record.title);
let document = ModelWeightDocument {
token: record.token,
creator_set_visibility: record.creator_set_visibility,
weights_type: record.weights_type,
weights_category: record.weights_category,
title: record.title,
maybe_cover_image_media_file_token: record.maybe_cover_image_media_file_token,
maybe_cover_image_public_bucket_hash: record.maybe_cover_image_public_bucket_hash,
maybe_cover_image_public_bucket_prefix: record.maybe_cover_image_public_bucket_prefix,
maybe_cover_image_public_bucket_extension: record.maybe_cover_image_public_bucket_extension,
creator_user_token: record.creator_user_token,
creator_username: record.creator_username,
creator_display_name: record.creator_display_name,
creator_gravatar_hash: record.creator_gravatar_hash,
ratings_positive_count: record.maybe_ratings_positive_count.unwrap_or(0),
ratings_negative_count: record.maybe_ratings_negative_count.unwrap_or(0),
bookmark_count: record.maybe_bookmark_count.unwrap_or(0),
maybe_ietf_language_tag: match record.weights_category {
WeightsCategory::TextToSpeech => record.maybe_tts_ietf_language_tag,
WeightsCategory::VoiceConversion => record.maybe_voice_conversion_ietf_language_tag,
_ => None,
},
maybe_ietf_primary_language_subtag: match record.weights_category {
WeightsCategory::TextToSpeech => record.maybe_tts_ietf_primary_language_subtag,
WeightsCategory::VoiceConversion => record.maybe_voice_conversion_ietf_primary_language_subtag,
_ => None,
},
created_at: record.created_at,
updated_at: record.updated_at,
user_deleted_at: record.user_deleted_at,
mod_deleted_at: record.mod_deleted_at,
};
let op : BulkOperation<_> = BulkOperation::index(&document)
.id(document.get_document_id())
.into();
let response = elasticsearch
.bulk(BulkParts::Index(MODEL_WEIGHT_INDEX))
.body(vec![op])
.send()
.await?;
let json: Value = response.json().await?;
let had_errors = json["errors"].as_bool().unwrap_or(false);
if had_errors {
let failed: Vec<&Value> = json["items"]
.as_array()
.unwrap()
.iter()
.filter(|v| !v["error"].is_null())
.collect();
error!("Errors during indexing. Failures: {}", failed.len());
return Err(anyhow!("Errors during indexing. Failures: {}", failed.len()));
}
Ok(())
}
+3 -1
View File
@@ -82,4 +82,6 @@ my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
time = "0.3.30"
mime = "0.3.17"
rand = { version = "0.8.5", features = [] }
jsonpath_lib = "0.3.0"
jsonpath_lib = "0.3.0"
walkdir = "2.4.0"
reqwest = "0.11.22"
@@ -114,6 +114,10 @@ RERENDER_INFERENCE_EXECUTABLE_OR_COMMAND="venv/bin/python rerender.py"
# ================================= #
STABLE_DIFFUSION_INFERENCE_ROOT_DIRECTORY="/home/tensor/code/storyteller/storyteller-ml/image_generation/Storyteller-Studio-Filter/"
STABLE_DIFFUSION_INFERENCE_COMMAND=".venv/bin/python main.py"
SD_VAE_BUCKET_PATH="/home/tensor/code/storyteller/seed-tool-data/models/imagegen/vae/vaeFtMse840000EmaPruned_vae.safetensors"
SD_PREDEFINED_SD_WEIGHT_TOKEN=""
SD_PREDEFINED_LORA_WEIGHT_TOKEN=""
# ================================= #
# #
@@ -133,6 +137,15 @@ COMFY_INFERENCE_EXECUTABLE_OR_COMMAND="venv/bin/python ../ComfyLauncher/ComfyRun
STYLE_TTS2_INFERENCE_ROOT_DIRECTORY="/home/salt/clone/storyteller-ml/tts/style-tts2"
STYLE_TTS2_INFERENCE_COMMAND="venv/bin/python3.8 main.py"
# ================================= #
# #
# Fbx2Gltf #
# #
# ================================= #
#FBX2GLTF_DIRECTORY="%storyteller_root%/FBX2glTF"
FBX2GLTF_DIRECTORY="/home/bt/dev/storyteller/FBX2glTF"
FBX2GLTF_EXECUTABLE_OR_COMMAND="./bin/FBX2glTF-linux-x64"
# ================================= #
# #
# CACHE SETTINGS #
@@ -1,5 +1,4 @@
pub mod clear_full_filesystem;
pub mod command_exit_status;
pub mod determine_dependency_status;
pub mod job_success_result;
pub mod main_loop;
@@ -13,12 +13,13 @@ use redis_common::redis_keys::RedisKeys;
use crate::job::job_loop::determine_dependency_status::determine_dependency_status;
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
use crate::job::job_loop::process_single_job_success_case::ProcessSingleJobSuccessCase;
use crate::job::job_types::format_conversion::process_single_format_conversion_job::process_single_format_conversion_job;
use crate::job::job_types::image_generation::process_single_ig_job::process_single_ig_job;
use crate::job::job_types::lipsync::process_single_lipsync_job::process_single_lipsync_job;
use crate::job::job_types::mocap::process_single_mc_job::process_single_mc_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_vf_job;
use crate::job::job_types::image_generation::process_single_ig_job::process_single_ig_job;
use crate::job::job_types::mocap::process_single_mc_job::process_single_mc_job;
use crate::job::job_types::workflow::process_single_wf_job::process_single_wf_job;
use crate::job_dependencies::JobDependencies;
@@ -198,6 +199,9 @@ async fn do_process_single_job(
InferenceCategory::Workflow => {
process_single_wf_job(job_dependencies, job).await?
}
InferenceCategory::FormatConversion => {
process_single_format_conversion_job(job_dependencies, job).await?
}
};
let maybe_entity_type = job_success_result.maybe_result_entity
@@ -0,0 +1,33 @@
use std::path::Path;
use filesys::path_to_string::path_to_string;
use subprocess_common::command_runner::command_args::CommandArgs;
pub struct FbxToGltfCommandArgs<'a> {
pub input_file: &'a Path,
pub output_directory: &'a Path,
/// If true, output as one `.glb` binary file. If false, output two files: one `.gltf` and one `.bin` file.
pub binary: bool,
}
impl CommandArgs for FbxToGltfCommandArgs<'_> {
fn to_command_string(&self) -> String {
let mut command = String::new();
// Verbose flag
command.push_str(" -v ");
command.push_str(" -i ");
command.push_str(&path_to_string(self.input_file));
command.push_str(" -o ");
command.push_str(&path_to_string(self.output_directory));
if self.binary {
command.push_str(" --binary ");
}
command
}
}
@@ -0,0 +1,46 @@
use std::path::PathBuf;
use errors::AnyhowResult;
use subprocess_common::command_runner::command_runner::CommandRunner;
use subprocess_common::command_runner::env_var_policy::EnvVarPolicy;
use subprocess_common::docker_options::{DockerFilesystemMount, DockerOptions};
use subprocess_common::executable_or_command::ExecutableOrShellCommand;
pub struct FbxToGltfDependencies {
pub command_runner: CommandRunner,
}
impl FbxToGltfDependencies {
pub fn setup() -> AnyhowResult<Self> {
let maybe_execution_directory = easyenv::get_env_pathbuf_optional("FBX2GLTF_DIRECTORY");
let executable_or_command = easyenv::get_env_string_required("FBX2GLTF_EXECUTABLE_OR_COMMAND")?;
let executable_or_command = if executable_or_command.contains(" ") {
ExecutableOrShellCommand::BashShellCommand(executable_or_command)
} else {
ExecutableOrShellCommand::Executable(PathBuf::from(executable_or_command))
};
let maybe_execution_timeout = easyenv::get_env_duration_seconds_optional("FBX2GLTF_TIMEOUT_SECONDS");
let maybe_docker_options = easyenv::get_env_string_optional("FBX2GLTF_DOCKER_IMAGE")
.map(|image_name| DockerOptions {
image_name,
maybe_bind_mount: Some(DockerFilesystemMount::tmp_to_tmp()),
maybe_environment_variables: None,
maybe_gpu: None,
});
Ok(Self {
command_runner: CommandRunner {
executable_or_command,
maybe_execution_directory,
env_var_policy: EnvVarPolicy::CopyNone,
maybe_virtual_env_activation_command: None,
maybe_docker_options,
maybe_execution_timeout,
}
})
}
}
@@ -0,0 +1,3 @@
pub mod command_args;
pub mod dependencies;
pub mod process_job;
@@ -0,0 +1,405 @@
use std::fs::read_to_string;
use std::path::PathBuf;
use std::time::Instant;
use anyhow::anyhow;
use log::{error, info, warn};
use buckets::public::media_files::bucket_file_path::MediaFileBucketPath;
use enums::by_table::generic_inference_jobs::inference_result_type::InferenceResultType;
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_product_category::MediaFileOriginProductCategory;
use enums::by_table::media_files::media_file_type::MediaFileType;
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 jobs_common::job_progress_reporter::job_progress_reporter::JobProgressReporter;
use mimetypes::mimetype_for_file::get_mimetype_for_file;
use mysql_queries::queries::generic_inference::job::list_available_generic_inference_jobs::AvailableInferenceJob;
use mysql_queries::queries::media_files::create::insert_media_file_generic::{insert_media_file_generic, InsertArgs};
use mysql_queries::queries::media_files::get_media_file_for_inference::MediaFileForInference;
use subprocess_common::command_runner::command_runner_args::{FileOrCreate, RunAsSubprocessArgs};
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::format_conversion::fbx_to_gltf::command_args::FbxToGltfCommandArgs;
use crate::job_dependencies::JobDependencies;
use crate::util::maybe_download_file_from_bucket::{maybe_download_file_from_bucket, MaybeDownloadArgs};
// Flag to control GLB vs GLTF uploading
const UPLOAD_AS_GLB : bool = true;
// GLTF is two files - one json, one binary
const BUCKET_GLTF_FILE_EXTENSION: &str = ".gltf";
const BUFFER_BIN_FILE_EXTENSION : &str = ".bin";
// GLB is one binary file
const BUCKET_GLB_FILE_EXTENSION: &str = ".glb";
pub struct FbxToGltfJobArgs<'a> {
pub job_dependencies: &'a JobDependencies,
pub job: &'a AvailableInferenceJob,
pub media_file: &'a MediaFileForInference,
}
//noinspection RsConstantConditionIf
pub async fn process_job(args: FbxToGltfJobArgs<'_>) -> Result<JobSuccessResult, ProcessSingleJobError> {
let job = args.job;
let media_file = args.media_file;
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_convert_fbx_to_gltf_dependencies
.as_ref()
.ok_or_else(|| ProcessSingleJobError::JobSystemMisconfiguration(Some("missing ConvertFbx2Gltf dependencies".to_string())))?;
// ==================== TEMP DIR ==================== //
let work_temp_dir = format!("temp_file_convert_{}", 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))?;
// ==================== DOWNLOAD MEDIA FILE ==================== //
info!("Download media for conversion...");
let original_media_upload_fs_path = {
let original_media_file_fs_path = work_temp_dir.path().join("original.fbx");
let media_file_bucket_path = MediaFileBucketPath::from_object_hash(
&media_file.public_bucket_directory_hash,
media_file.maybe_public_bucket_prefix.as_deref(),
media_file.maybe_public_bucket_extension.as_deref());
let bucket_object_path = media_file_bucket_path.to_full_object_pathbuf();
info!("Downloading media to bucket path: {:?}", &bucket_object_path);
maybe_download_file_from_bucket(MaybeDownloadArgs {
name_or_description_of_file: "media upload (original file)",
final_filesystem_file_path: &original_media_file_fs_path,
bucket_object_path: &bucket_object_path,
bucket_client: &args.job_dependencies.buckets.public_bucket_client,
job_progress_reporter: &mut job_progress_reporter,
job_progress_update_description: "downloading",
job_id: job.id.0,
scoped_tempdir_creator: &args.job_dependencies.fs.scoped_temp_dir_creator_for_work,
maybe_existing_file_minimum_size_required: None,
}).await?;
original_media_file_fs_path
};
// ==================== SETUP FOR CONVERSION ==================== //
job_progress_reporter.log_status("running conversion")
.map_err(|e| ProcessSingleJobError::Other(e))?;
// ==================== RUN INFERENCE SCRIPT ==================== //
let stderr_output_file = work_temp_dir.path().join("stderr.txt");
let output_directory = work_temp_dir.path().join("output");
// NB: gltf gets put into a subdirectory with a name based on the passed argument, whereas glb gets dropped into
// the same directory
let output_directory_actual = match UPLOAD_AS_GLB {
true => work_temp_dir.path().to_path_buf(),
false => work_temp_dir.path().join("output_out"),
};
let execution_start_time = Instant::now();
let command_exit_status = {
model_dependencies
.command_runner
.run_with_subprocess(RunAsSubprocessArgs {
args: Box::new(&FbxToGltfCommandArgs {
input_file: &original_media_upload_fs_path,
output_directory: &output_directory,
binary: true,
}),
maybe_stderr_output_file: Some(FileOrCreate::NewFileWithName(&stderr_output_file)),
maybe_stdout_output_file: None,
})
};
let execution_duration = Instant::now().duration_since(execution_start_time);
info!("Execution took duration to complete: {:?}", &execution_duration);
if !command_exit_status.is_success() {
error!("Execution 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(&original_media_upload_fs_path);
safe_delete_temp_directory(&output_directory_actual);
safe_delete_temp_directory(&work_temp_dir);
return Err(error);
}
let upload_details = if UPLOAD_AS_GLB {
validate_and_upload_glb(&args, &mut job_progress_reporter, &output_directory_actual).await?
} else {
validate_and_upload_gltf(&args, &mut job_progress_reporter, &output_directory_actual).await?
};
// ==================== CLEANUP FILES ==================== //
safe_delete_temp_directory(&output_directory_actual);
safe_delete_temp_directory(&work_temp_dir);
// ==================== SAVE RECORDS ==================== //
info!("Saving record (only gltf or glb, not the buffer.bin) ...");
let (inference_result_token, id) = insert_media_file_generic(InsertArgs {
pool: &args.job_dependencies.db.mysql_pool,
job: &job,
media_type: upload_details.media_type,
origin_category: MediaFileOriginCategory::Processed,
origin_product_category: MediaFileOriginProductCategory::Mocap,
maybe_origin_model_type: None,
maybe_origin_model_token: None,
maybe_origin_filename: None,
maybe_mime_type: upload_details.maybe_mimetype.as_deref(),
file_size_bytes: upload_details.size_bytes,
maybe_duration_millis: None,
maybe_audio_encoding: None,
maybe_video_encoding: None,
maybe_frame_width: None,
maybe_frame_height: None,
public_bucket_directory_hash: upload_details.bucket_location.get_object_hash(),
maybe_public_bucket_prefix: upload_details.bucket_location.get_optional_prefix(),
maybe_public_bucket_extension: upload_details.bucket_location.get_optional_extension(),
extra_file_modification_info: None,
maybe_creator_file_synthetic_id_category: IdCategory::MediaFile,
maybe_creator_category_synthetic_id_category: IdCategory::MocapResult,
maybe_mod_user_token: None,
is_batch_generated: false,
is_generated_on_prem: false,
checksum_sha2: &upload_details.checksum,
generated_by_worker: Some(&args.job_dependencies.job.info.container.hostname),
generated_by_cluster: Some(&args.job_dependencies.job.info.container.cluster_name),
})
.await
.map_err(|e| ProcessSingleJobError::Other(e))?;
info!("Fbx2Gltf Done.");
//args.job_dependencies.clients.firehose_publisher.vc_inference_finished(
// maybe_user_token.as_ref(),
// &job.inference_job_token,
// inference_result_token.as_str())
// .await
// .map_err(|e| {
// error!("error publishing event: {:?}", e);
// ProcessSingleJobError::Other(anyhow!("error publishing event"))
// })?;
job_progress_reporter.log_status("done")
.map_err(|e| ProcessSingleJobError::Other(e))?;
info!("Job {:?} complete success! Downloaded, executed successfully, and uploaded. Saved record: {}, Result Token: {}",
job.id, id, &inference_result_token);
Ok(JobSuccessResult {
maybe_result_entity: Some(ResultEntity {
entity_type: InferenceResultType::MediaFile,
entity_token: inference_result_token.to_string(),
}),
inference_duration: execution_duration,
})
}
struct UploadDetails {
media_type: MediaFileType,
size_bytes: u64,
maybe_mimetype: Option<String>,
checksum: String,
bucket_location: MediaFileBucketPath,
}
async fn validate_and_upload_glb(args: &FbxToGltfJobArgs<'_>, job_progress_reporter: &mut Box<dyn JobProgressReporter>,
output_directory_actual: &PathBuf) -> Result<UploadDetails, ProcessSingleJobError>
{
// ==================== CHECK ALL FILES EXIST AND GET METADATA ==================== //
// NB: FBX2GLTF creates multiple files in the directory
// NB: The actual name of the file will be these:
let output_file = output_directory_actual.join("output.glb");
info!("Checking that output files exist...");
check_file_exists(&output_file).map_err(|e| ProcessSingleJobError::Other(e))?;
info!("Interrogating result file properties...");
let file_size_bytes = file_size(&output_file)
.map_err(|err| ProcessSingleJobError::Other(err))?;
let maybe_mimetype = get_mimetype_for_file(&output_file)
.map_err(|err| ProcessSingleJobError::from_io_error(err))?
.map(|mime| mime.to_string());
info!("Calculating sha256...");
let file_checksum = sha256_hash_file(&output_file)
.map_err(|err| {
ProcessSingleJobError::Other(anyhow!("Error hashing file: {:?}", err))
})?;
// ==================== UPLOAD GLB TO BUCKET ==================== //
job_progress_reporter.log_status("uploading output.glb")
.map_err(|e| ProcessSingleJobError::Other(e))?;
let bucket_location = MediaFileBucketPath::generate_new(
None,
Some(BUCKET_GLTF_FILE_EXTENSION));
let bucket_object_pathbuf = bucket_location.to_full_object_pathbuf();
info!("Destination bucket path: {:?}", &bucket_object_pathbuf);
info!("Uploading...");
args.job_dependencies.buckets.public_bucket_client.upload_filename_with_content_type(
&bucket_object_pathbuf,
&output_file,
"model/gltf+binary")
.await
.map_err(|e| ProcessSingleJobError::Other(e))?;
// Cleanup
safe_delete_temp_file(&output_file);
Ok(UploadDetails {
media_type: MediaFileType::Glb,
size_bytes: file_size_bytes,
maybe_mimetype,
checksum: file_checksum,
bucket_location,
})
}
async fn validate_and_upload_gltf(args: &FbxToGltfJobArgs<'_>, job_progress_reporter: &mut Box<dyn JobProgressReporter>,
output_directory_actual: &PathBuf) -> Result<UploadDetails, ProcessSingleJobError>
{
// ==================== CHECK ALL FILES EXIST AND GET METADATA ==================== //
// NB: FBX2GLTF creates multiple files in the directory
// NB: The actual name of the file will be these:
let output_gltf_file = output_directory_actual.join("output.gltf");
let output_buffer_file = output_directory_actual.join("buffer.bin");
info!("Checking that output files exist...");
check_file_exists(&output_gltf_file).map_err(|e| ProcessSingleJobError::Other(e))?;
check_file_exists(&output_buffer_file).map_err(|e| ProcessSingleJobError::Other(e))?;
info!("Interrogating result file properties...");
let gltf_file_size_bytes = file_size(&output_gltf_file)
.map_err(|err| ProcessSingleJobError::Other(err))?;
let maybe_gltf_mimetype = get_mimetype_for_file(&output_gltf_file)
.map_err(|err| ProcessSingleJobError::from_io_error(err))?
.map(|mime| mime.to_string());
info!("Calculating sha256...");
let gltf_file_checksum = sha256_hash_file(&output_gltf_file)
.map_err(|err| {
ProcessSingleJobError::Other(anyhow!("Error hashing file: {:?}", err))
})?;
// ==================== UPLOAD GLTF TO BUCKET ==================== //
job_progress_reporter.log_status("uploading output.gltf")
.map_err(|e| ProcessSingleJobError::Other(e))?;
let result_gtlf_bucket_location = MediaFileBucketPath::generate_new(
None,
Some(BUCKET_GLTF_FILE_EXTENSION));
let result_gltf_bucket_object_pathbuf = result_gtlf_bucket_location.to_full_object_pathbuf();
info!("Destination bucket path: {:?}", &result_gltf_bucket_object_pathbuf);
info!("Uploading...");
args.job_dependencies.buckets.public_bucket_client.upload_filename_with_content_type(
&result_gltf_bucket_object_pathbuf,
&output_gltf_file,
"model/gltf+json")
.await
.map_err(|e| ProcessSingleJobError::Other(e))?;
// ==================== UPLOAD BIN TO BUCKET ==================== //
job_progress_reporter.log_status("uploading buffer.bin")
.map_err(|e| ProcessSingleJobError::Other(e))?;
// NB: Reuse same object hash, just change the extension.
let result_buffer_bucket_location = MediaFileBucketPath::from_object_hash(
result_gtlf_bucket_location.get_object_hash(),
None,
Some(BUFFER_BIN_FILE_EXTENSION));
let result_buffer_bucket_object_pathbuf = result_buffer_bucket_location.to_full_object_pathbuf();
info!("Destination bucket path: {:?}", &result_buffer_bucket_object_pathbuf);
info!("Uploading...");
args.job_dependencies.buckets.public_bucket_client.upload_filename_with_content_type(
&result_buffer_bucket_object_pathbuf,
&output_buffer_file,
"application/octet-stream")
.await
.map_err(|e| ProcessSingleJobError::Other(e))?;
// Cleanup
safe_delete_temp_file(&output_buffer_file);
safe_delete_temp_file(&output_gltf_file);
Ok(UploadDetails {
media_type: MediaFileType::Gltf,
size_bytes: gltf_file_size_bytes,
maybe_mimetype: maybe_gltf_mimetype,
checksum: gltf_file_checksum,
bucket_location: result_gtlf_bucket_location,
})
}
@@ -0,0 +1,2 @@
pub mod fbx_to_gltf;
pub mod process_single_format_conversion_job;
@@ -0,0 +1,63 @@
use anyhow::anyhow;
use log::error;
use enums::by_table::generic_inference_jobs::inference_model_type::InferenceModelType;
use mysql_queries::queries::generic_inference::job::list_available_generic_inference_jobs::AvailableInferenceJob;
use mysql_queries::queries::media_files::get_media_file_for_inference::get_media_file_for_inference;
use tokens::tokens::media_files::MediaFileToken;
use crate::job::job_loop::job_success_result::JobSuccessResult;
use crate::job::job_loop::process_single_job_error::ProcessSingleJobError;
use crate::job::job_types::format_conversion::fbx_to_gltf;
use crate::job::job_types::format_conversion::fbx_to_gltf::process_job::FbxToGltfJobArgs;
use crate::job_dependencies::JobDependencies;
// // NB: There will likely be lots of other format conversions for us to run, and there's probably lots of shared code between them.
// // This enum lets us reuse the shared portions in this function before dispatching to the special parts.
// #[derive(Debug, Copy, Clone)]
// pub enum FormatConversionType {
// FbxToGltf,
// }
pub async fn process_single_format_conversion_job(job_dependencies: &JobDependencies, job: &AvailableInferenceJob) -> Result<JobSuccessResult, ProcessSingleJobError> {
let maybe_media_file_token = job.maybe_input_source_token
.as_deref()
.map(|token| MediaFileToken::new_from_str(token));
let media_file_token = match maybe_media_file_token {
None => return Err(ProcessSingleJobError::Other(anyhow!("no associated media file for format conversion job: {:?}", job.inference_job_token))),
Some(token) => token,
};
let maybe_media_file_result =
get_media_file_for_inference(&media_file_token, &job_dependencies.db.mysql_pool).await;
let media_file = match maybe_media_file_result {
Ok(Some(media_file)) => media_file,
Ok(None) => {
error!("no media file record found for token: {:?}", media_file_token);
return Err(ProcessSingleJobError::Other(anyhow!("no media file record found for token: {:?}", media_file_token)));
}
Err(err) => {
error!("error fetching media file record from db: {:?}", err);
return Err(ProcessSingleJobError::Other(err));
}
};
let job_success_result = match job.maybe_model_type {
Some(InferenceModelType::ConvertFbxToGltf) => {
fbx_to_gltf::process_job::process_job(FbxToGltfJobArgs {
job_dependencies,
job,
media_file: &media_file,
}).await?
},
Some(model_type) => return Err(ProcessSingleJobError::Other(anyhow!("wrong model type: {:?}", model_type))),
None => return Err(ProcessSingleJobError::Other(anyhow!("no model type in record"))),
};
Ok(job_success_result)
}
@@ -1,63 +1,52 @@
use std::convert::TryInto;
use std::fs::read_to_string;
use std::path::PathBuf;
use std::{ fs, thread };
use std::time::{ Duration, Instant };
use std::time::{Duration, Instant};
use actix_web::dev::ResourcePath;
use cloud_storage::remote_file_manager::weights_descriptor::{WeightsLoRADescriptor, WeightsSD15Descriptor};
use filesys::file_exists::file_exists;
use serde_json;
use anyhow::anyhow;
use log::{error, info, warn};
use serde_json;
use cloud_storage::remote_file_manager::media_descriptor::MediaImagePngDescriptor;
use cloud_storage::remote_file_manager::remote_cloud_bucket_details::RemoteCloudBucketDetails;
use cloud_storage::remote_file_manager::remote_cloud_file_manager::RemoteCloudFileClient;
use cloud_storage::remote_file_manager::weights_descriptor::{WeightsLoRADescriptor, WeightsSD15Descriptor};
use composite_identifiers::by_table::batch_generations::batch_generation_entity::BatchGenerationEntity;
use enums::by_table::generic_inference_jobs::inference_result_type::InferenceResultType;
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 log::info;
use cloud_storage::remote_file_manager::media_descriptor::MediaImagePngDescriptor;
use cloud_storage::remote_file_manager::remote_cloud_bucket_details::RemoteCloudBucketDetails;
use cloud_storage::remote_file_manager::remote_cloud_file_manager::{self, RemoteCloudFileClient};
use enums::by_table::media_files::media_file_type::MediaFileType;
use enums::by_table::model_weights::weights_category::WeightsCategory;
use enums::by_table::model_weights::weights_types::WeightsType;
use filesys::file_exists::file_exists;
use filesys::path_to_string::path_to_string;
use google_drive_common::google_drive_download_command::GoogleDriveDownloadCommand;
use mysql_queries::payloads::generic_inference_args::generic_inference_args::PolymorphicInferenceArgs;
use mysql_queries::payloads::generic_inference_args::image_generation_payload::StableDiffusionArgs;
use mysql_queries::queries::batch_generations::insert_batch_generation_records::{
InsertBatchArgs,
self,
};
use mysql_queries::queries::batch_generations::insert_batch_generation_records::insert_batch_generation_records;
use mysql_queries::queries::batch_generations::insert_batch_generation_records::InsertBatchArgs;
use mysql_queries::queries::generic_inference::job::list_available_generic_inference_jobs::AvailableInferenceJob;
use mysql_queries::queries::media_files::create::insert_media_file_from_file_upload::{
insert_media_file_from_file_upload,
InsertMediaFileFromUploadArgs,
UploadType,
};
use mysql_queries::queries::media_files::create::insert_media_file_generic::insert_media_file_generic;
use mysql_queries::queries::media_files::create::insert_media_file_generic::InsertArgs;
use mysql_queries::queries::model_categories::create_category;
use mysql_queries::queries::model_weights::create::create_weight::{
create_weight,
CreateModelWeightsArgs,
};
use mysql_queries::queries::model_weights::get::get_weight::get_weight_by_token;
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::model_weights::ModelWeightToken;
use tokens::tokens::users::UserToken;
use crate::job::job_loop::job_success_result::{ JobSuccessResult, ResultEntity };
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::image_generation::sd::sd_inference_command::InferenceArgs;
use crate::job_dependencies::JobDependencies;
const SD_IMAGE_PREFIX : &str = "image_";
const SD_IMAGE_SUFFIX : &str = ".png";
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)]
pub struct InferenceValues {
pub prompt: String,
@@ -90,6 +79,7 @@ pub async fn sd_args_from_job(
| args.args.as_ref()
)
.flatten();
let polymorphic_args = match inference_args {
Some(args) => args,
None => {
@@ -107,6 +97,7 @@ pub async fn sd_args_from_job(
);
}
};
let stable_diffusion_args: StableDiffusionArgs = StableDiffusionArgs::from(sd_args.clone());
Ok(stable_diffusion_args)
}
@@ -118,11 +109,11 @@ pub async fn process_job_selection(
let sd_args = sd_args_from_job(&args).await?;
if sd_args.type_of_inference == "inference" {
return process_job_inference(&args).await
process_job_inference(&args).await
} else if sd_args.type_of_inference == "lora" {
return process_job_lora(&args).await
process_job_lora(&args).await
} else if sd_args.type_of_inference == "model" {
return process_job_sd(&args).await
process_job_sd(&args).await
} else {
Err(ProcessSingleJobError::Other(anyhow!("inference type doesn't exist!")))
}
@@ -168,13 +159,14 @@ pub async fn process_job_sd(
.map_err(|e| ProcessSingleJobError::from_io_error(e))?;
let sd_checkpoint_path = work_temp_dir.path().join("sd_checkpoint.safetensors");
let vae_path = work_temp_dir.path().join("vae.safetensors");
//let vae_path = work_temp_dir.path().join("vae.safetensors");
let vae_path = work_temp_dir.path().join("vae.pt"); // TODO: Should this be `.safetensors` or `.pt`?
let output_path = work_temp_dir.path().join("output");
info!("Paths to download to:");
info!("sd_checkpoint_path:{}", sd_checkpoint_path.display());
info!("vae_path:{}", vae_path.display());
info!("output_path:{}", output_path.display());
info!("sd_checkpoint_path: {:?}", sd_checkpoint_path);
info!("vae_path: {:?}", vae_path);
info!("output_path: {:?}", output_path);
let download_url = match sd_args.maybe_upload_path {
Some(val) => { val }
@@ -192,12 +184,12 @@ pub async fn process_job_sd(
"DOWNLOAD_SCRIPT",
"download_internet_file.py"
);
// Download
let google_drive_downloader = GoogleDriveDownloadCommand::new(&download_script,
None,
None,
None);
None, None, None);
info!("Downloading {}", download_url);
let download_filename = match
google_drive_downloader.download_file_with_file_name(
&download_url,
@@ -206,59 +198,69 @@ pub async fn process_job_sd(
).await
{
Ok(filename) => filename,
Err(_e) => {
return Err(ProcessSingleJobError::from_anyhow_error(anyhow!("Failed to Download")));
}
Err(_e) => return Err(ProcessSingleJobError::from_anyhow_error(anyhow!("Failed to Download"))),
};
let download_file_path = work_temp_dir.path().join(download_filename);
if file_exists(download_file_path.as_path()) == false {
return Err(
ProcessSingleJobError::from_anyhow_error(anyhow!("Failed to Download SD Model from Google"))
);
return Err(ProcessSingleJobError::from_anyhow_error(anyhow!("Failed to Download SD Model from Google")));
}
info!("File Retrieved at {}", download_file_path.display());
// download vae for model
// use this vae doesn't matter though
// VAE token for now
let vae_token = String::from("weight_rb0959wfzjhk3d1k93hr3s0qw");
let model_weight_vae = ModelWeightToken(vae_token);
let vae_weight_record = get_weight_by_token(
&model_weight_vae,
false,
&deps.db.mysql_pool
).await?;
let vae_weight_record = match vae_weight_record {
Some(val) => val,
None => {
return Err(
ProcessSingleJobError::from_anyhow_error(anyhow!("no VAE? thats a problem."))
);
}
};
args.job_dependencies
.buckets
.public_bucket_client
.download_file_to_disk(&sd_deps.vae_bucket_path, &vae_path)
.await
.map_err(|err| {
error!("could not download VAE: {:?}", err);
ProcessSingleJobError::from_anyhow_error(anyhow!("could not download VAE: {:?}", err))
})?;
let vae_details = RemoteCloudBucketDetails::new(
vae_weight_record.public_bucket_hash.clone(),
vae_weight_record.maybe_public_bucket_prefix.clone().unwrap_or_else(|| "".to_string()),
vae_weight_record.maybe_public_bucket_extension.clone().unwrap_or_else(|| "".to_string())
);
info!("VAE downloaded to: {:?}", &vae_path);
// // use this vae doesn't matter though
// // VAE token for now
// let vae_token = String::from("REPLACE_ME");
// let model_weight_vae = ModelWeightToken(vae_token);
// let vae_weight_record = get_weight_by_token(
// &model_weight_vae,
// false,
// &deps.db.mysql_pool
// ).await?;
// let vae_weight_record = match vae_weight_record {
// Some(val) => val,
// None => {
// return Err(
// ProcessSingleJobError::from_anyhow_error(anyhow!("no VAE? thats a problem."))
// );
// }
// };
//
// let vae_details = RemoteCloudBucketDetails::new(
// vae_weight_record.public_bucket_hash.clone(),
// vae_weight_record.maybe_public_bucket_prefix.clone().unwrap_or_else(|| "".to_string()),
// vae_weight_record.maybe_public_bucket_extension.clone().unwrap_or_else(|| "".to_string())
// );
//
// let remote_cloud_file_client = RemoteCloudFileClient::get_remote_cloud_file_client().await?;
// remote_cloud_file_client.download_file(vae_details, path_to_string(vae_path.clone())).await?;
let remote_cloud_file_client = RemoteCloudFileClient::get_remote_cloud_file_client().await?;
remote_cloud_file_client.download_file(vae_details, path_to_string(vae_path.clone())).await?;
let stderr_output_file = work_temp_dir.path().join("sd_err.txt");
let stdout_output_file = work_temp_dir.path().join("sd_out.txt");
// run inference on loRA downloaded
sd_deps.inference_command.execute_inference(InferenceArgs {
let exit_status = sd_deps.inference_command.execute_inference(InferenceArgs {
work_dir: work_temp_dir.path().to_path_buf(),
output_file: output_path.clone(),
stderr_output_file,
stdout_output_file,
stderr_output_file: &stderr_output_file,
stdout_output_file: &stdout_output_file,
prompt:String::from("This is a green sign that says go this is a test prompt to test the model."),
negative_prompt: sd_args.maybe_n_prompt.clone().unwrap_or_default(),
number_of_samples:20,
@@ -273,14 +275,38 @@ pub async fn process_job_sd(
batch_count: sd_args.maybe_batch_count.unwrap_or(1),
});
if !exit_status.is_success() {
error!("SD inference failed: {:?}", exit_status);
let error = ProcessSingleJobError::Other(anyhow!("CommandExitStatus: {:?}", 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;
// }
// _ => {}
//}
}
return Err(error);
}
// check if file exists if it does not error out....
let path = output_path.clone();
let file_path = format!("{}_{}.png", path_to_string(path), 0);
if file_exists(file_path.path()) == false {
return Err(
ProcessSingleJobError::from_anyhow_error(anyhow!("Failed to Upload Not a SD Model"))
);
if !file_exists(file_path.path()) {
error!("Output file did not exist: {:?}", file_path);
if let Ok(contents) = read_to_string(&stderr_output_file) {
error!("Captured stderr output: {}", contents);
}
return Err(ProcessSingleJobError::from_anyhow_error(anyhow!("Failed to Upload Not a SD Model")));
}
// If it worked and didn't fail! then we should save and create the weight.
@@ -309,7 +335,8 @@ pub async fn process_job_sd(
maybe_last_update_user_token: None,
original_download_url: Some(download_url),
original_filename: None,
file_size_bytes: metadata.file_size_bytes,
// file_size_bytes: metadata.file_size_bytes, // TODO(bt,2024-02-03): We need to migrate the column to be BIGINT
file_size_bytes: 0,
file_checksum_sha2: metadata.sha256_checksum,
public_bucket_hash: bucket_details.object_hash,
maybe_public_bucket_prefix: Some(bucket_details.prefix),
@@ -340,23 +367,15 @@ pub async fn process_job_lora(
let sd_deps = match
&args.job_dependencies.job.job_specific_dependencies.maybe_stable_diffusion_dependencies
{
None => {
return Err(ProcessSingleJobError::Other(anyhow!("Missing Job Specific Dependencies")));
}
Some(val) => { val }
Some(val) => val,
None => return Err(ProcessSingleJobError::Other(anyhow!("Missing Job Specific Dependencies"))),
};
let creator_ip_address = &job.creator_ip_address;
let creator_user_token: UserToken;
match &job.maybe_creator_user_token {
Some(token) => {
creator_user_token = UserToken::new_from_str(token);
}
None => {
return Err(ProcessSingleJobError::InvalidJob(anyhow!("Missing Creator User Token")));
}
}
let creator_user_token = match job.maybe_creator_user_token.as_deref() {
Some(token) => UserToken::new_from_str(token),
None => return Err(ProcessSingleJobError::InvalidJob(anyhow!("Missing Creator User Token"))),
};
let work_temp_dir = format!("temp_stable_diffusion_inference_{}", job.id.0);
@@ -366,14 +385,15 @@ pub async fn process_job_lora(
let sd_checkpoint_path = work_temp_dir.path().join("sd_checkpoint.safetensors");
let lora_path = work_temp_dir.path().join("lora.safetensors"); // input path into execution
let vae_path = work_temp_dir.path().join("vae.safetensors");
//let vae_path = work_temp_dir.path().join("vae.safetensors");
let vae_path = work_temp_dir.path().join("vae.pt"); // TODO: Should this be `.safetensors` or `.pt`?
let output_path = work_temp_dir.path().join("output");
info!("Paths to download to:");
info!("sd_checkpoint_path:{}", sd_checkpoint_path.display());
info!("lora_path:{}", lora_path.display());
info!("vae_path:{}", vae_path.display());
info!("output_path:{}", output_path.display());
info!("sd_checkpoint_path: {:?}", sd_checkpoint_path);
info!("lora_path: {:?}", lora_path);
info!("vae_path: {:?}", vae_path);
info!("output_path: {:?}", output_path);
let download_url = match sd_args.maybe_lora_upload_path {
Some(val) => { val }
@@ -425,7 +445,10 @@ pub async fn process_job_lora(
// download model + vae for lora
// use lora with whatever checkpoint make it fixed id if it works submit it
let weight_token_sd_model_token = ModelWeightToken(String::from("weight_dmmthavhawqc2hj7yqyemcbf8")); // any generic model.
let weight_token_sd_model_token = ModelWeightToken::new_from_str(&sd_deps.predefined_sd_weight_token);
info!("Using predefined SD weight token: {:?}", &weight_token_sd_model_token);
let sd_weight_record = get_weight_by_token(
&weight_token_sd_model_token,
false,
@@ -450,42 +473,52 @@ pub async fn process_job_lora(
remote_cloud_file_client.download_file(sd_weight_details, path_to_string(sd_checkpoint_path.clone())).await?;
// use this vae doesn't matter though
// VAE token for now
let vae_token = String::from("weight_rb0959wfzjhk3d1k93hr3s0qw");
let model_weight_vae = ModelWeightToken(vae_token);
let vae_weight_record = get_weight_by_token(
&model_weight_vae,
false,
&deps.db.mysql_pool
).await?;
let vae_weight_record = match vae_weight_record {
Some(val) => val,
None => {
return Err(
ProcessSingleJobError::from_anyhow_error(anyhow!("no VAE? thats a problem."))
);
}
};
args.job_dependencies
.buckets
.public_bucket_client
.download_file_to_disk(&sd_deps.vae_bucket_path, &vae_path)
.await
.map_err(|err| {
error!("could not download VAE: {:?}", err);
ProcessSingleJobError::from_anyhow_error(anyhow!("could not download VAE: {:?}", err))
})?;
let vae_details = RemoteCloudBucketDetails::new(
vae_weight_record.public_bucket_hash.clone(),
vae_weight_record.maybe_public_bucket_prefix.clone().unwrap_or_else(|| "".to_string()),
vae_weight_record.maybe_public_bucket_extension.clone().unwrap_or_else(|| "".to_string())
);
let remote_cloud_file_client = RemoteCloudFileClient::get_remote_cloud_file_client().await?;
remote_cloud_file_client.download_file(vae_details, path_to_string(vae_path.clone())).await?;
// // use this vae doesn't matter though
// // VAE token for now
// let vae_token = String::from("REPLACE_ME");
// let model_weight_vae = ModelWeightToken(vae_token);
// let vae_weight_record = get_weight_by_token(
// &model_weight_vae,
// false,
// &deps.db.mysql_pool
// ).await?;
// let vae_weight_record = match vae_weight_record {
// Some(val) => val,
// None => {
// return Err(
// ProcessSingleJobError::from_anyhow_error(anyhow!("no VAE? thats a problem."))
// );
// }
// };
//
// let vae_details = RemoteCloudBucketDetails::new(
// vae_weight_record.public_bucket_hash.clone(),
// vae_weight_record.maybe_public_bucket_prefix.clone().unwrap_or_else(|| "".to_string()),
// vae_weight_record.maybe_public_bucket_extension.clone().unwrap_or_else(|| "".to_string())
// );
//
// let remote_cloud_file_client = RemoteCloudFileClient::get_remote_cloud_file_client().await?;
// remote_cloud_file_client.download_file(vae_details, path_to_string(vae_path.clone())).await?;
let stderr_output_file = work_temp_dir.path().join("sd_err.txt");
let stdout_output_file = work_temp_dir.path().join("sd_out.txt");
// run inference on loRA downloaded
sd_deps.inference_command.execute_inference(InferenceArgs {
let exit_status = sd_deps.inference_command.execute_inference(InferenceArgs {
work_dir: work_temp_dir.path().to_path_buf(),
output_file: output_path.clone(),
stderr_output_file,
stdout_output_file,
stderr_output_file: &stderr_output_file,
stdout_output_file: &stdout_output_file,
prompt:String::from("This is a green sign that says go this is a test prompt to test the model."),
negative_prompt: sd_args.maybe_n_prompt.clone().unwrap_or_default(),
number_of_samples:20,
@@ -500,6 +533,25 @@ pub async fn process_job_lora(
batch_count: sd_args.maybe_batch_count.unwrap_or(1),
});
if !exit_status.is_success() {
error!("SD inference failed: {:?}", exit_status);
let error = ProcessSingleJobError::Other(anyhow!("CommandExitStatus: {:?}", 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;
// }
// _ => {}
//}
}
return Err(error);
}
// check if file exists if it does not error out....
let path = output_path.clone();
let file_path = format!("{}_{}.png", path_to_string(path), 0);
@@ -573,22 +625,22 @@ pub async fn process_job_inference(
Some(val) => { val }
};
let creator_user_token: UserToken;
let anon_user_token: Option<AnonymousVisitorTrackingToken>;
let _creator_user_token: UserToken;
let _anon_user_token: Option<AnonymousVisitorTrackingToken>;
match &job.maybe_creator_anonymous_visitor_token {
Some(token) => {
let anonymous_visitor_token = AnonymousVisitorTrackingToken::new_from_str(token);
anon_user_token = Some(anonymous_visitor_token);
_anon_user_token = Some(anonymous_visitor_token);
}
None => {
anon_user_token = None;
_anon_user_token = None;
}
}
match &job.maybe_creator_user_token {
Some(token) => {
creator_user_token = UserToken::new_from_str(token);
_creator_user_token = UserToken::new_from_str(token);
}
None => {
return Err(ProcessSingleJobError::InvalidJob(anyhow!("Missing Creator User Token")));
@@ -610,14 +662,15 @@ pub async fn process_job_inference(
let sd_checkpoint_path = work_temp_dir.path().join("sd_checkpoint.safetensors");
let mut lora_path = work_temp_dir.path().join("lora.safetensors");
let vae_path = work_temp_dir.path().join("vae.safetensors");
//let vae_path = work_temp_dir.path().join("vae.safetensors");
let vae_path = work_temp_dir.path().join("vae.pt"); // TODO: Should this be `.safetensors` or `.pt`?
let output_path = work_temp_dir.path().join("output");
info!("Paths to download to:");
info!("sd_checkpoint_path:{}", sd_checkpoint_path.display());
info!("lora_path:{}", lora_path.display());
info!("vae_path:{}", vae_path.display());
info!("output_path:{}", output_path.display());
info!("sd_checkpoint_path: {:?}", sd_checkpoint_path);
info!("lora_path: {:?}", lora_path);
info!("vae_path: {:?}", vae_path);
info!("output_path: {:?}", output_path);
// thread::sleep(seconds) to check the directory
@@ -711,32 +764,42 @@ pub async fn process_job_inference(
}
}
// VAE token for now
let vae_token = String::from("weight_rb0959wfzjhk3d1k93hr3s0qw");
let model_weight_vae = ModelWeightToken(vae_token);
args.job_dependencies
.buckets
.public_bucket_client
.download_file_to_disk(&sd_deps.vae_bucket_path, &vae_path)
.await
.map_err(|err| {
error!("could not download VAE: {:?}", err);
ProcessSingleJobError::from_anyhow_error(anyhow!("could not download VAE: {:?}", err))
})?;
let vae_weight_record = get_weight_by_token(
&model_weight_vae,
false,
&deps.db.mysql_pool
).await?;
let vae_weight_record = match vae_weight_record {
Some(val) => val,
None => {
return Err(
ProcessSingleJobError::from_anyhow_error(anyhow!("no VAE? thats a problem."))
);
}
};
let vae_details = RemoteCloudBucketDetails::new(
vae_weight_record.public_bucket_hash.clone(),
vae_weight_record.maybe_public_bucket_prefix.clone().unwrap_or_else(|| "".to_string()),
vae_weight_record.maybe_public_bucket_extension.clone().unwrap_or_else(|| "".to_string())
);
remote_cloud_file_client.download_file(vae_details, path_to_string(vae_path.clone())).await?;
// // VAE token for now
// let vae_token = String::from("REPLACE_ME");
// let model_weight_vae = ModelWeightToken(vae_token);
//
// let vae_weight_record = get_weight_by_token(
// &model_weight_vae,
// false,
// &deps.db.mysql_pool
// ).await?;
//
// let vae_weight_record = match vae_weight_record {
// Some(val) => val,
// None => {
// return Err(
// ProcessSingleJobError::from_anyhow_error(anyhow!("no VAE? thats a problem."))
// );
// }
// };
//
// let vae_details = RemoteCloudBucketDetails::new(
// vae_weight_record.public_bucket_hash.clone(),
// vae_weight_record.maybe_public_bucket_prefix.clone().unwrap_or_else(|| "".to_string()),
// vae_weight_record.maybe_public_bucket_extension.clone().unwrap_or_else(|| "".to_string())
// );
//
// remote_cloud_file_client.download_file(vae_details, path_to_string(vae_path.clone())).await?;
let prompt = match sd_args.maybe_prompt {
Some(val) => val,
@@ -755,11 +818,11 @@ pub async fn process_job_inference(
let inference_start_time = Instant::now();
sd_deps.inference_command.execute_inference(InferenceArgs {
let exit_status = sd_deps.inference_command.execute_inference(InferenceArgs {
work_dir: work_temp_dir.path().to_path_buf(),
output_file: output_path.clone(),
stderr_output_file,
stdout_output_file,
stderr_output_file: &stderr_output_file,
stdout_output_file: &stdout_output_file,
prompt: prompt.clone(),
negative_prompt: sd_args.maybe_n_prompt.clone().unwrap_or_default(),
number_of_samples,
@@ -774,16 +837,33 @@ pub async fn process_job_inference(
batch_count: sd_args.maybe_batch_count.unwrap_or(1),
});
if !exit_status.is_success() {
error!("SD inference failed: {:?}", exit_status);
let error = ProcessSingleJobError::Other(anyhow!("CommandExitStatus: {:?}", exit_status));
// hack to check the directory before clean up.
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;
// }
// _ => {}
//}
}
return Err(error);
}
// hack to check the directory before clean up.
// let thirtyMinutes = 1800;
// thread::sleep(Duration::from_secs(thirtyMinutes));
// upload media and create a record.
let inference_duration = Instant::now().duration_since(inference_start_time);
let creator_ip_address = &job.creator_ip_address;
// run a for loop for output images output_0 in the folder then use upload media.
// pngs
@@ -799,12 +879,12 @@ pub async fn process_job_inference(
width: sd_args.maybe_width.unwrap_or(512),
height: sd_args.maybe_height.unwrap_or(512),
seed: sd_args.maybe_seed.unwrap_or(1),
number_of_samples: number_of_samples,
number_of_samples,
};
let inputs = match serde_json::to_string(&inputs) {
Ok(result) => result,
Err(err) => {
Err(_err) => {
return Err(
ProcessSingleJobError::from_anyhow_error(anyhow!("couldn't serialize metadata."))
);
@@ -833,10 +913,11 @@ pub async fn process_job_inference(
}
};
// extra_file_modification_info: todo!(), // JSON ENCODED STRUCT
let media_file_token = insert_media_file_generic(InsertArgs {
pool: mysql_pool,
job: job,
job,
media_type: MediaFileType::Image,
origin_category: MediaFileOriginCategory::Upload,
origin_product_category: MediaFileOriginProductCategory::ImageGeneration,
@@ -849,17 +930,15 @@ pub async fn process_job_inference(
maybe_duration_millis: Some(inference_duration.as_millis() as u64),
maybe_audio_encoding: None,
maybe_video_encoding: None,
maybe_frame_width: Some(sd_args.maybe_width.unwrap_or(512) as u32),
maybe_frame_height: Some(sd_args.maybe_height.unwrap_or(512) as u32),
maybe_frame_width: Some(sd_args.maybe_width.unwrap_or(512)),
maybe_frame_height: Some(sd_args.maybe_height.unwrap_or(512)),
checksum_sha2: metadata.sha256_checksum.as_str(),
public_bucket_directory_hash: bucket_details.object_hash.as_str(),
maybe_public_bucket_prefix: Some(bucket_details.prefix.as_str()),
maybe_public_bucket_extension: Some(bucket_details.suffix.as_str()),
//maybe_public_bucket_prefix: Some(bucket_details.prefix.as_str()),
maybe_public_bucket_prefix: Some(SD_IMAGE_PREFIX),
//maybe_public_bucket_extension: Some(bucket_details.suffix.as_str()),
maybe_public_bucket_extension: Some(SD_IMAGE_SUFFIX),
extra_file_modification_info: Some(&inputs),
maybe_creator_user_token: Some(&creator_user_token),
maybe_creator_anonymous_visitor_token: anon_user_token.as_ref(),
creator_ip_address: creator_ip_address,
creator_set_visibility: args.job.creator_set_visibility,
maybe_creator_file_synthetic_id_category: IdCategory::MediaFile,
maybe_creator_category_synthetic_id_category: IdCategory::ModelWeights,
maybe_mod_user_token: None,
@@ -883,7 +962,7 @@ pub async fn process_job_inference(
let batch_token = match batch_token_result {
Ok(v) => { v.to_string() }
Err(err) => {
Err(_err) => {
return Err(
ProcessSingleJobError::from_anyhow_error(
anyhow!("No batch token? something has failed.")
@@ -897,7 +976,7 @@ pub async fn process_job_inference(
entity_type: InferenceResultType::MediaFile,
entity_token: batch_token,
}),
inference_duration: inference_duration,
inference_duration,
})
}
@@ -914,9 +993,6 @@ mod tests {
};
use errors::AnyhowResult;
#[ignore]
#[tokio::test]
async fn test_json_extra_info() -> AnyhowResult<()> {}
#[ignore]
#[tokio::test]
async fn test_seed_weights_files() -> AnyhowResult<()> {
@@ -1,4 +1,3 @@
use std::collections::HashSet;
use std::env;
use std::ffi::OsString;
@@ -13,10 +12,9 @@ use subprocess::{Popen, PopenConfig, Redirection};
use errors::AnyhowResult;
use filesys::path_to_string::path_to_string;
use subprocess_common::command_exit_status::CommandExitStatus;
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(|| {
@@ -66,14 +64,14 @@ pub enum ExecutableOrCommand {
Command(String),
}
pub struct InferenceArgs {
pub struct InferenceArgs<'a> {
/// --source_image: path to the input image (or video)
/// --result_dir: path to directory work is performed
pub work_dir: PathBuf,
/// --result_file: path to final file output
pub output_file: PathBuf,
pub stdout_output_file: PathBuf,
pub stderr_output_file: PathBuf,
pub stdout_output_file: &'a PathBuf,
pub stderr_output_file: &'a PathBuf,
pub prompt: String,
pub negative_prompt:String,
pub number_of_samples:u32,
@@ -274,10 +272,11 @@ impl StableDiffusionInferenceCommand {
info!("stderr will be written to file: {}", path_to_string(args.stderr_output_file.clone()));
let stderr_file = File::create(&args.stderr_output_file)?;
let stdout_file = File::create(&args.stdout_output_file)?;
//let stdout_file = File::create(args.stdout_output_file)?;
//config.stdout = Redirection::File(stdout_file);
let stderr_file = File::create(args.stderr_output_file)?;
config.stderr = Redirection::File(stderr_file);
config.stdout = Redirection::File(stdout_file);
if !env_vars.is_empty() {
config.env = Some(env_vars);

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