Compare commits

..
1 Commits
Author SHA1 Message Date
Brandon Thomas cf9f1bdd93 ffprobe 2024-05-29 00:20:49 -04:00
781 changed files with 9643 additions and 16287 deletions
+7 -7
View File
@@ -26,14 +26,14 @@ MIGRATION_DIRECTORY=_sql/migrations/
# Bucket config (non-secret)
REGION_NAME=us-east1
#TTS_DOWNLOAD_BUCKET_NAME=kasisnu-t
#TTS_DOWNLOAD_BUCKET_ROOT=/user_uploaded_tts_models
TTS_DOWNLOAD_BUCKET_NAME=dev-vocodes-private-uploads
TTS_DOWNLOAD_BUCKET_ROOT=/user_uploaded_tts_models
#W2L_PRIVATE_DOWNLOAD_BUCKET_NAME=dev-vocodes-private-uploads
#W2L_PUBLIC_DOWNLOAD_BUCKET_NAME=dev-vocodes-public
W2L_PRIVATE_DOWNLOAD_BUCKET_NAME=dev-vocodes-private-uploads
W2L_PUBLIC_DOWNLOAD_BUCKET_NAME=dev-vocodes-public
#PRIVATE_BUCKET_NAME=kasisnu-test-area-private
#PUBLIC_BUCKET_NAME=kasisnu-test-area-public
PRIVATE_BUCKET_NAME=dev-vocodes-private-uploads
PUBLIC_BUCKET_NAME=dev-vocodes-public
# Bucket roots
AUDIO_UPLOADS_BUCKET_ROOT=/user_uploaded_w2l_audio
@@ -64,7 +64,7 @@ HIFIGAN_ROOT_CODE_DIRECTORY=/home/bt/dev/storyteller/storyteller-ml/tts
HIFIGAN_VIRTUAL_ENV_ACTIVATION_COMMAND="source python/bin/activate"
HIFIGAN_MODEL_CHECK_SCRIPT_NAME="vocodes_model_check_hifigan.py"
#; DOWNLOAD_SCRIPT="/home/tensor/code/storyteller/web-downloader/.venv/bin/python3.11 /home/tensor/code/storyteller/web-downloader/download_internet_file.py"
DOWNLOAD_SCRIPT="/home/tensor/code/storyteller/web-downloader/.venv/bin/python3.11 /home/tensor/code/storyteller/web-downloader/download_internet_file.py"
TTS_INFERENCE_SIDECAR_HOSTNAME=localhost:8000
@@ -0,0 +1,47 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n maybe_model_type as queue_type,\n count(*) as pending_job_count,\n NOW() as present_time\n FROM (\n SELECT\n token,\n maybe_model_type\n FROM generic_inference_jobs\n WHERE status IN (\"pending\", \"attempt_failed\")\n AND maybe_model_type IS NOT NULL\n UNION\n SELECT\n token,\n maybe_model_type\n FROM generic_inference_jobs\n WHERE status IN (\"started\")\n AND created_at > (CURDATE() - INTERVAL 15 MINUTE)\n AND maybe_model_type IS NOT NULL\n) as generic_inner\nGROUP BY queue_type\nUNION\nSELECT\n \"legacy_tts\" as queue_type,\n count(*) as pending_job_count,\n NOW() as present_time\nFROM (\n SELECT\n token\n FROM tts_inference_jobs\n WHERE status IN (\"pending\", \"attempt_failed\")\n UNION\n SELECT\n token\n FROM tts_inference_jobs\n WHERE status IN (\"started\")\n AND created_at > (CURDATE() - INTERVAL 15 MINUTE)\n) as legacy_inner\nGROUP BY queue_type\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "queue_type",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 1,
"name": "pending_job_count",
"type_info": {
"type": "LongLong",
"flags": "NOT_NULL | BINARY",
"char_set": 63,
"max_size": 21
}
},
{
"ordinal": 2,
"name": "present_time",
"type_info": {
"type": "Datetime",
"flags": "NOT_NULL",
"char_set": 224,
"max_size": 76
}
}
],
"parameters": {
"Right": 0
},
"nullable": [
true,
false,
false
]
},
"hash": "074307d7cd5c9a6f27e4d5ecce3072dcd13914a6f7f5f27f0f7232ae6c4813b4"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n token as `token: tokens::tokens::users::UserToken`,\n username,\n email_address,\n password_hash as `password_hash: crate::queries::users::user::lookup_user_for_login_result::VecBytes`,\n password_version,\n is_banned,\n maybe_feature_flags\nFROM users\nWHERE username = ?\nLIMIT 1\n ",
"query": "\nSELECT\n token as `token: tokens::tokens::users::UserToken`,\n username,\n email_address,\n password_hash as `password_hash: crate::queries::users::user::lookup_user_for_login_result::VecBytes`,\n password_version,\n is_banned\nFROM users\nWHERE username = ?\nLIMIT 1\n ",
"describe": {
"columns": [
{
@@ -62,16 +62,6 @@
"char_set": 63,
"max_size": 1
}
},
{
"ordinal": 6,
"name": "maybe_feature_flags",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
}
],
"parameters": {
@@ -83,9 +73,8 @@
false,
false,
false,
false,
true
false
]
},
"hash": "8e577db7abde571a0f29fc9d678891349918f0882136d9f7a39b338f66270f11"
"hash": "5795b8579a458706077b1fa95e4627b90eac6606bde543b6406233d081fd0eb5"
}
@@ -1,47 +0,0 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n job_type as queue_type,\n count(*) as pending_job_count,\n NOW() as present_time\n FROM (\n SELECT\n token,\n job_type\n FROM generic_inference_jobs\n WHERE status IN (\"pending\", \"attempt_failed\")\n AND job_type IS NOT NULL\n UNION\n SELECT\n token,\n job_type\n FROM generic_inference_jobs\n WHERE status IN (\"started\")\n AND created_at > (CURDATE() - INTERVAL 15 MINUTE)\n AND job_type IS NOT NULL\n) as generic_inner\nGROUP BY queue_type\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "queue_type",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 1,
"name": "pending_job_count",
"type_info": {
"type": "LongLong",
"flags": "NOT_NULL | BINARY",
"char_set": 63,
"max_size": 21
}
},
{
"ordinal": 2,
"name": "present_time",
"type_info": {
"type": "Datetime",
"flags": "NOT_NULL | BINARY",
"char_set": 63,
"max_size": 19
}
}
],
"parameters": {
"Right": 0
},
"nullable": [
true,
false,
false
]
},
"hash": "73dc054488b2fa3efc923d3a90914a1450b73e99a377562484a0498ccce1676f"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n\n jobs.status as `status: enums::common::job_status_plus::JobStatusPlus`,\n jobs.attempt_count,\n\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n jobs.creator_ip_address,\n\n jobs.inference_category as `inference_category: enums::by_table::generic_inference_jobs::inference_category::InferenceCategory`,\n jobs.maybe_model_type,\n jobs.maybe_model_token,\n jobs.maybe_raw_inference_text,\n jobs.maybe_inference_args,\n\n jobs.frontend_failure_category as `maybe_frontend_failure_category: enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory`,\n\n jobs.on_success_result_entity_type as maybe_result_entity_type,\n jobs.on_success_result_entity_token as maybe_result_entity_token,\n\n model_weights.title as maybe_model_weights_title,\n tts_models.title as maybe_tts_model_title,\n voice_conversion_models.title as maybe_voice_conversion_model_title,\n\n tts_results.public_bucket_wav_audio_path as maybe_tts_public_bucket_path,\n voice_conversion_results.public_bucket_hash as maybe_voice_conversion_public_bucket_hash,\n\n media_files.public_bucket_directory_hash as maybe_media_file_public_bucket_directory_hash,\n media_files.maybe_public_bucket_prefix as maybe_media_file_public_bucket_prefix,\n media_files.maybe_public_bucket_extension as maybe_media_file_public_bucket_extension,\n\n jobs.assigned_worker as maybe_assigned_worker,\n jobs.assigned_cluster as maybe_assigned_cluster,\n\n jobs.is_keepalive_required,\n\n jobs.created_at,\n jobs.updated_at,\n\n jobs.first_started_at as maybe_first_started_at,\n jobs.successfully_completed_at as maybe_successfully_completed_at,\n\n NOW() as database_clock\n\nFROM generic_inference_jobs as jobs\n\nLEFT OUTER JOIN model_weights ON jobs.maybe_model_token = model_weights.token\nLEFT OUTER JOIN tts_models ON jobs.maybe_model_token = tts_models.token\nLEFT OUTER JOIN voice_conversion_models ON jobs.maybe_model_token = voice_conversion_models.token\n\nLEFT OUTER JOIN tts_results ON jobs.on_success_result_entity_token = tts_results.token\nLEFT OUTER JOIN voice_conversion_results ON jobs.on_success_result_entity_token = voice_conversion_results.token\nLEFT OUTER JOIN media_files ON jobs.on_success_result_entity_token = media_files.token\n\nWHERE jobs.token = ?\n ",
"query": "\nSELECT\n jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n\n jobs.status as `status: enums::common::job_status_plus::JobStatusPlus`,\n jobs.attempt_count,\n\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n jobs.creator_ip_address,\n\n jobs.inference_category as `inference_category: enums::by_table::generic_inference_jobs::inference_category::InferenceCategory`,\n jobs.maybe_model_type,\n jobs.maybe_model_token,\n jobs.maybe_raw_inference_text,\n jobs.maybe_inference_args,\n\n jobs.frontend_failure_category as `maybe_frontend_failure_category: enums::by_table::generic_inference_jobs::frontend_failure_category::FrontendFailureCategory`,\n\n jobs.on_success_result_entity_type as maybe_result_entity_type,\n jobs.on_success_result_entity_token as maybe_result_entity_token,\n\n model_weights.title as maybe_model_weights_title,\n tts_models.title as maybe_tts_model_title,\n voice_conversion_models.title as maybe_voice_conversion_model_title,\n\n tts_results.public_bucket_wav_audio_path as maybe_tts_public_bucket_path,\n voice_conversion_results.public_bucket_hash as maybe_voice_conversion_public_bucket_hash,\n\n media_files.public_bucket_directory_hash as maybe_media_file_public_bucket_directory_hash,\n media_files.maybe_public_bucket_prefix as maybe_media_file_public_bucket_prefix,\n media_files.maybe_public_bucket_extension as maybe_media_file_public_bucket_extension,\n\n jobs.assigned_worker as maybe_assigned_worker,\n jobs.assigned_cluster as maybe_assigned_cluster,\n\n jobs.is_keepalive_required,\n\n jobs.created_at,\n jobs.updated_at,\n\n jobs.first_started_at as maybe_first_started_at,\n jobs.successfully_completed_at as maybe_successfully_completed_at\n\nFROM generic_inference_jobs as jobs\n\nLEFT OUTER JOIN model_weights ON jobs.maybe_model_token = model_weights.token\nLEFT OUTER JOIN tts_models ON jobs.maybe_model_token = tts_models.token\nLEFT OUTER JOIN voice_conversion_models ON jobs.maybe_model_token = voice_conversion_models.token\n\nLEFT OUTER JOIN tts_results ON jobs.on_success_result_entity_token = tts_results.token\nLEFT OUTER JOIN voice_conversion_results ON jobs.on_success_result_entity_token = voice_conversion_results.token\nLEFT OUTER JOIN media_files ON jobs.on_success_result_entity_token = media_files.token\n\nWHERE jobs.token = ?\n ",
"describe": {
"columns": [
{
@@ -292,16 +292,6 @@
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 29,
"name": "database_clock",
"type_info": {
"type": "Datetime",
"flags": "NOT_NULL | BINARY",
"char_set": 63,
"max_size": 19
}
}
],
"parameters": {
@@ -336,9 +326,8 @@
false,
false,
true,
true,
false
true
]
},
"hash": "e5121d132d93ec43e48197e00b19040a50ff2942224dc70b0faa0ec988aee7cf"
"hash": "7d5030b221e9b470ee7af4b73539fd2ef477e6f3c98c56422016449c6775222d"
}
@@ -1,12 +0,0 @@
{
"db_name": "MySQL",
"query": "\nUPDATE media_files\nSET\n maybe_animation_type = ?\nWHERE\n token = ?\nLIMIT 1\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "930cfd52f78fa14a86bd1a67c5fe36d8c8961079b2bd684a0d4da315bf3ce16e"
}
@@ -1,12 +0,0 @@
{
"db_name": "MySQL",
"query": "\nUPDATE generic_inference_jobs\nSET\n is_dismissed_by_user = TRUE\nWHERE maybe_creator_user_token = ?\nAND status IN (\n 'dead',\n 'cancelled_by_system',\n 'cancelled_by_user',\n 'complete_failure',\n 'complete_success'\n )\nAND created_at > DATE_SUB(NOW(), INTERVAL 36 HOUR)\nLIMIT 1000\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "a1d44611ee4a0ecc54548f95719f01341eb242460c79ef75423de96b31ed5385"
}
@@ -1,12 +0,0 @@
{
"db_name": "MySQL",
"query": "\nINSERT INTO users (\n token,\n username,\n display_name,\n email_address,\n email_gravatar_hash,\n profile_markdown,\n profile_rendered_html,\n user_role_slug,\n password_hash,\n ip_address_creation,\n ip_address_last_login,\n ip_address_last_update,\n can_access_studio\n)\nVALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? , ?)\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 13
},
"nullable": []
},
"hash": "a3066dd68c77b5381104a754b2774506990f659c9a0ed2de1fcb8e6fd953e726"
}
@@ -1,12 +0,0 @@
{
"db_name": "MySQL",
"query": "\nUPDATE media_files\nSET\n maybe_engine_category = ?\nWHERE\n token = ?\nLIMIT 1\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "b56ee40be840cdec013f3f99eb585e4ac333ccfe563ba3da4c9a80df33dccd57"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.maybe_engine_category as `maybe_engine_category: enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory`,\n m.maybe_animation_type as `maybe_animation_type: enums::by_table::media_files::media_file_animation_type::MediaFileAnimationType`,\n\n m.maybe_media_subtype as `maybe_media_subtype: enums::by_table::media_files::media_file_subtype::MediaFileSubtype`,\n\n m.maybe_mime_type,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n m.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n\n m.maybe_batch_token as `maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken`,\n\n m.maybe_style_transfer_source_media_file_token as `maybe_style_transfer_source_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n m.maybe_scene_source_media_file_token as `maybe_scene_source_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n\n m.maybe_title,\n m.maybe_text_transcript,\n\n m.maybe_origin_filename,\n\n m.maybe_duration_millis,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n prompts.maybe_other_args as maybe_other_prompt_args,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n model_weights.token as `maybe_model_weights_token: tokens::tokens::model_weights::ModelWeightToken`,\n model_weights.title as maybe_model_weights_title,\n model_weights.weights_type as `maybe_model_weights_type: enums::by_table::model_weights::weights_types::WeightsType`,\n model_weights.weights_category as `maybe_model_weights_category: enums::by_table::model_weights::weights_category::WeightsCategory`,\n\n model_weight_cover_image.public_bucket_directory_hash as maybe_model_cover_image_public_bucket_hash,\n model_weight_cover_image.maybe_public_bucket_prefix as maybe_model_cover_image_public_bucket_prefix,\n model_weight_cover_image.maybe_public_bucket_extension as maybe_model_cover_image_public_bucket_extension,\n\n model_weight_creator.token as `maybe_model_weight_creator_user_token: tokens::tokens::users::UserToken`,\n model_weight_creator.username as maybe_model_weight_creator_username,\n model_weight_creator.display_name as maybe_model_weight_creator_display_name,\n model_weight_creator.email_gravatar_hash as maybe_model_weight_creator_gravatar_hash,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n entity_stats.ratings_positive_count as maybe_ratings_positive_count,\n entity_stats.ratings_negative_count as maybe_ratings_negative_count,\n entity_stats.bookmark_count as maybe_bookmark_count,\n\n featured_items.entity_token IS NOT NULL AS is_featured,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN model_weights\n ON m.maybe_origin_model_token = model_weights.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nLEFT OUTER JOIN media_files as model_weight_cover_image\n ON model_weight_cover_image.token = model_weights.maybe_cover_image_media_file_token\nLEFT OUTER JOIN users as model_weight_creator\n ON model_weight_creator.token = model_weights.creator_user_token\nLEFT OUTER JOIN entity_stats\n ON entity_stats.entity_type = \"media_file\"\n AND entity_stats.entity_token = m.token\nLEFT OUTER JOIN prompts\n ON prompts.token = m.maybe_prompt_token\nLEFT OUTER JOIN featured_items\n ON featured_items.entity_type = 'media_file'\n AND featured_items.entity_token = m.token\n AND featured_items.deleted_at IS NULL\nWHERE\n m.token = ?\n ",
"query": "\nSELECT\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.maybe_engine_category as `maybe_engine_category: enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory`,\n m.maybe_animation_type as `maybe_animation_type: enums::by_table::media_files::media_file_animation_type::MediaFileAnimationType`,\n\n m.maybe_media_subtype as `maybe_media_subtype: enums::by_table::media_files::media_file_subtype::MediaFileSubtype`,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n m.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n\n m.maybe_batch_token as `maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken`,\n\n m.maybe_style_transfer_source_media_file_token as `maybe_style_transfer_source_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n m.maybe_scene_source_media_file_token as `maybe_scene_source_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n\n\n m.maybe_title,\n m.maybe_text_transcript,\n\n m.maybe_origin_filename,\n\n m.maybe_duration_millis,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n prompts.maybe_other_args as maybe_other_prompt_args,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n model_weights.token as `maybe_model_weights_token: tokens::tokens::model_weights::ModelWeightToken`,\n model_weights.title as maybe_model_weights_title,\n model_weights.weights_type as `maybe_model_weights_type: enums::by_table::model_weights::weights_types::WeightsType`,\n model_weights.weights_category as `maybe_model_weights_category: enums::by_table::model_weights::weights_category::WeightsCategory`,\n\n model_weight_cover_image.public_bucket_directory_hash as maybe_model_cover_image_public_bucket_hash,\n model_weight_cover_image.maybe_public_bucket_prefix as maybe_model_cover_image_public_bucket_prefix,\n model_weight_cover_image.maybe_public_bucket_extension as maybe_model_cover_image_public_bucket_extension,\n\n model_weight_creator.token as `maybe_model_weight_creator_user_token: tokens::tokens::users::UserToken`,\n model_weight_creator.username as maybe_model_weight_creator_username,\n model_weight_creator.display_name as maybe_model_weight_creator_display_name,\n model_weight_creator.email_gravatar_hash as maybe_model_weight_creator_gravatar_hash,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n entity_stats.ratings_positive_count as maybe_ratings_positive_count,\n entity_stats.ratings_negative_count as maybe_ratings_negative_count,\n entity_stats.bookmark_count as maybe_bookmark_count,\n\n featured_items.entity_token IS NOT NULL AS is_featured,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN model_weights\n ON m.maybe_origin_model_token = model_weights.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nLEFT OUTER JOIN media_files as model_weight_cover_image\n ON model_weight_cover_image.token = model_weights.maybe_cover_image_media_file_token\nLEFT OUTER JOIN users as model_weight_creator\n ON model_weight_creator.token = model_weights.creator_user_token\nLEFT OUTER JOIN entity_stats\n ON entity_stats.entity_type = \"media_file\"\n AND entity_stats.entity_token = m.token\nLEFT OUTER JOIN prompts\n ON prompts.token = m.maybe_prompt_token\nLEFT OUTER JOIN featured_items\n ON featured_items.entity_type = 'media_file'\n AND featured_items.entity_token = m.token\n AND featured_items.deleted_at IS NULL\nWHERE\n m.token = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n ",
"describe": {
"columns": [
{
@@ -65,16 +65,6 @@
},
{
"ordinal": 6,
"name": "maybe_mime_type",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 7,
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
@@ -84,7 +74,7 @@
}
},
{
"ordinal": 8,
"ordinal": 7,
"name": "maybe_creator_username",
"type_info": {
"type": "VarString",
@@ -94,7 +84,7 @@
}
},
{
"ordinal": 9,
"ordinal": 8,
"name": "maybe_creator_display_name",
"type_info": {
"type": "VarString",
@@ -104,7 +94,7 @@
}
},
{
"ordinal": 10,
"ordinal": 9,
"name": "maybe_creator_gravatar_hash",
"type_info": {
"type": "String",
@@ -114,7 +104,7 @@
}
},
{
"ordinal": 11,
"ordinal": 10,
"name": "maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken",
"type_info": {
"type": "VarString",
@@ -124,7 +114,7 @@
}
},
{
"ordinal": 12,
"ordinal": 11,
"name": "maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken",
"type_info": {
"type": "VarString",
@@ -134,7 +124,7 @@
}
},
{
"ordinal": 13,
"ordinal": 12,
"name": "maybe_style_transfer_source_media_file_token: tokens::tokens::media_files::MediaFileToken",
"type_info": {
"type": "VarString",
@@ -144,7 +134,7 @@
}
},
{
"ordinal": 14,
"ordinal": 13,
"name": "maybe_scene_source_media_file_token: tokens::tokens::media_files::MediaFileToken",
"type_info": {
"type": "VarString",
@@ -154,7 +144,7 @@
}
},
{
"ordinal": 15,
"ordinal": 14,
"name": "maybe_title",
"type_info": {
"type": "VarString",
@@ -164,7 +154,7 @@
}
},
{
"ordinal": 16,
"ordinal": 15,
"name": "maybe_text_transcript",
"type_info": {
"type": "Blob",
@@ -174,7 +164,7 @@
}
},
{
"ordinal": 17,
"ordinal": 16,
"name": "maybe_origin_filename",
"type_info": {
"type": "VarString",
@@ -184,7 +174,7 @@
}
},
{
"ordinal": 18,
"ordinal": 17,
"name": "maybe_duration_millis",
"type_info": {
"type": "Long",
@@ -194,7 +184,7 @@
}
},
{
"ordinal": 19,
"ordinal": 18,
"name": "maybe_prompt_token: tokens::tokens::prompts::PromptToken",
"type_info": {
"type": "VarString",
@@ -204,7 +194,7 @@
}
},
{
"ordinal": 20,
"ordinal": 19,
"name": "maybe_other_prompt_args",
"type_info": {
"type": "Blob",
@@ -214,7 +204,7 @@
}
},
{
"ordinal": 21,
"ordinal": 20,
"name": "maybe_file_cover_image_public_bucket_hash",
"type_info": {
"type": "VarString",
@@ -224,7 +214,7 @@
}
},
{
"ordinal": 22,
"ordinal": 21,
"name": "maybe_file_cover_image_public_bucket_prefix",
"type_info": {
"type": "VarString",
@@ -234,7 +224,7 @@
}
},
{
"ordinal": 23,
"ordinal": 22,
"name": "maybe_file_cover_image_public_bucket_extension",
"type_info": {
"type": "VarString",
@@ -244,7 +234,7 @@
}
},
{
"ordinal": 24,
"ordinal": 23,
"name": "creator_set_visibility: enums::common::visibility::Visibility",
"type_info": {
"type": "String",
@@ -254,7 +244,7 @@
}
},
{
"ordinal": 25,
"ordinal": 24,
"name": "maybe_model_weights_token: tokens::tokens::model_weights::ModelWeightToken",
"type_info": {
"type": "VarString",
@@ -264,7 +254,7 @@
}
},
{
"ordinal": 26,
"ordinal": 25,
"name": "maybe_model_weights_title",
"type_info": {
"type": "VarString",
@@ -274,7 +264,7 @@
}
},
{
"ordinal": 27,
"ordinal": 26,
"name": "maybe_model_weights_type: enums::by_table::model_weights::weights_types::WeightsType",
"type_info": {
"type": "VarString",
@@ -284,7 +274,7 @@
}
},
{
"ordinal": 28,
"ordinal": 27,
"name": "maybe_model_weights_category: enums::by_table::model_weights::weights_category::WeightsCategory",
"type_info": {
"type": "VarString",
@@ -294,7 +284,7 @@
}
},
{
"ordinal": 29,
"ordinal": 28,
"name": "maybe_model_cover_image_public_bucket_hash",
"type_info": {
"type": "VarString",
@@ -304,7 +294,7 @@
}
},
{
"ordinal": 30,
"ordinal": 29,
"name": "maybe_model_cover_image_public_bucket_prefix",
"type_info": {
"type": "VarString",
@@ -314,7 +304,7 @@
}
},
{
"ordinal": 31,
"ordinal": 30,
"name": "maybe_model_cover_image_public_bucket_extension",
"type_info": {
"type": "VarString",
@@ -324,7 +314,7 @@
}
},
{
"ordinal": 32,
"ordinal": 31,
"name": "maybe_model_weight_creator_user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
@@ -334,7 +324,7 @@
}
},
{
"ordinal": 33,
"ordinal": 32,
"name": "maybe_model_weight_creator_username",
"type_info": {
"type": "VarString",
@@ -344,7 +334,7 @@
}
},
{
"ordinal": 34,
"ordinal": 33,
"name": "maybe_model_weight_creator_display_name",
"type_info": {
"type": "VarString",
@@ -354,7 +344,7 @@
}
},
{
"ordinal": 35,
"ordinal": 34,
"name": "maybe_model_weight_creator_gravatar_hash",
"type_info": {
"type": "String",
@@ -364,7 +354,7 @@
}
},
{
"ordinal": 36,
"ordinal": 35,
"name": "public_bucket_directory_hash",
"type_info": {
"type": "VarString",
@@ -374,7 +364,7 @@
}
},
{
"ordinal": 37,
"ordinal": 36,
"name": "maybe_public_bucket_prefix",
"type_info": {
"type": "VarString",
@@ -384,7 +374,7 @@
}
},
{
"ordinal": 38,
"ordinal": 37,
"name": "maybe_public_bucket_extension",
"type_info": {
"type": "VarString",
@@ -394,7 +384,7 @@
}
},
{
"ordinal": 39,
"ordinal": 38,
"name": "maybe_ratings_positive_count",
"type_info": {
"type": "Long",
@@ -404,7 +394,7 @@
}
},
{
"ordinal": 40,
"ordinal": 39,
"name": "maybe_ratings_negative_count",
"type_info": {
"type": "Long",
@@ -414,7 +404,7 @@
}
},
{
"ordinal": 41,
"ordinal": 40,
"name": "maybe_bookmark_count",
"type_info": {
"type": "Long",
@@ -424,7 +414,7 @@
}
},
{
"ordinal": 42,
"ordinal": 41,
"name": "is_featured",
"type_info": {
"type": "LongLong",
@@ -434,7 +424,7 @@
}
},
{
"ordinal": 43,
"ordinal": 42,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -444,7 +434,7 @@
}
},
{
"ordinal": 44,
"ordinal": 43,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -481,7 +471,6 @@
true,
true,
true,
true,
false,
true,
true,
@@ -505,5 +494,5 @@
false
]
},
"hash": "f1861dc84015b5c753cb5c31313c747386bce56983108fd6a01815d02d429630"
"hash": "beda05b1f3e3f78ca2610c491e682ea1c76fb5effccd5d710761353baf8ce161"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.maybe_engine_category as `maybe_engine_category: enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory`,\n m.maybe_animation_type as `maybe_animation_type: enums::by_table::media_files::media_file_animation_type::MediaFileAnimationType`,\n\n m.maybe_media_subtype as `maybe_media_subtype: enums::by_table::media_files::media_file_subtype::MediaFileSubtype`,\n\n m.maybe_mime_type,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n m.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n\n m.maybe_batch_token as `maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken`,\n\n m.maybe_style_transfer_source_media_file_token as `maybe_style_transfer_source_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n m.maybe_scene_source_media_file_token as `maybe_scene_source_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n\n\n m.maybe_title,\n m.maybe_text_transcript,\n\n m.maybe_origin_filename,\n\n m.maybe_duration_millis,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n prompts.maybe_other_args as maybe_other_prompt_args,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n model_weights.token as `maybe_model_weights_token: tokens::tokens::model_weights::ModelWeightToken`,\n model_weights.title as maybe_model_weights_title,\n model_weights.weights_type as `maybe_model_weights_type: enums::by_table::model_weights::weights_types::WeightsType`,\n model_weights.weights_category as `maybe_model_weights_category: enums::by_table::model_weights::weights_category::WeightsCategory`,\n\n model_weight_cover_image.public_bucket_directory_hash as maybe_model_cover_image_public_bucket_hash,\n model_weight_cover_image.maybe_public_bucket_prefix as maybe_model_cover_image_public_bucket_prefix,\n model_weight_cover_image.maybe_public_bucket_extension as maybe_model_cover_image_public_bucket_extension,\n\n model_weight_creator.token as `maybe_model_weight_creator_user_token: tokens::tokens::users::UserToken`,\n model_weight_creator.username as maybe_model_weight_creator_username,\n model_weight_creator.display_name as maybe_model_weight_creator_display_name,\n model_weight_creator.email_gravatar_hash as maybe_model_weight_creator_gravatar_hash,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n entity_stats.ratings_positive_count as maybe_ratings_positive_count,\n entity_stats.ratings_negative_count as maybe_ratings_negative_count,\n entity_stats.bookmark_count as maybe_bookmark_count,\n\n featured_items.entity_token IS NOT NULL AS is_featured,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN model_weights\n ON m.maybe_origin_model_token = model_weights.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nLEFT OUTER JOIN media_files as model_weight_cover_image\n ON model_weight_cover_image.token = model_weights.maybe_cover_image_media_file_token\nLEFT OUTER JOIN users as model_weight_creator\n ON model_weight_creator.token = model_weights.creator_user_token\nLEFT OUTER JOIN entity_stats\n ON entity_stats.entity_type = \"media_file\"\n AND entity_stats.entity_token = m.token\nLEFT OUTER JOIN prompts\n ON prompts.token = m.maybe_prompt_token\nLEFT OUTER JOIN featured_items\n ON featured_items.entity_type = 'media_file'\n AND featured_items.entity_token = m.token\n AND featured_items.deleted_at IS NULL\nWHERE\n m.token = ?\n AND m.user_deleted_at IS NULL\n AND m.mod_deleted_at IS NULL\n ",
"query": "\nSELECT\n m.token as `token: tokens::tokens::media_files::MediaFileToken`,\n\n m.media_class as `media_class: enums::by_table::media_files::media_file_class::MediaFileClass`,\n m.media_type as `media_type: enums::by_table::media_files::media_file_type::MediaFileType`,\n\n m.maybe_engine_category as `maybe_engine_category: enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory`,\n m.maybe_animation_type as `maybe_animation_type: enums::by_table::media_files::media_file_animation_type::MediaFileAnimationType`,\n\n m.maybe_media_subtype as `maybe_media_subtype: enums::by_table::media_files::media_file_subtype::MediaFileSubtype`,\n\n users.token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n users.username as maybe_creator_username,\n users.display_name as maybe_creator_display_name,\n users.email_gravatar_hash as maybe_creator_gravatar_hash,\n\n m.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n\n m.maybe_batch_token as `maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken`,\n\n m.maybe_style_transfer_source_media_file_token as `maybe_style_transfer_source_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n m.maybe_scene_source_media_file_token as `maybe_scene_source_media_file_token: tokens::tokens::media_files::MediaFileToken`,\n\n m.maybe_title,\n m.maybe_text_transcript,\n\n m.maybe_origin_filename,\n\n m.maybe_duration_millis,\n\n m.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n prompts.maybe_other_args as maybe_other_prompt_args,\n\n media_file_cover_image.public_bucket_directory_hash as maybe_file_cover_image_public_bucket_hash,\n media_file_cover_image.maybe_public_bucket_prefix as maybe_file_cover_image_public_bucket_prefix,\n media_file_cover_image.maybe_public_bucket_extension as maybe_file_cover_image_public_bucket_extension,\n\n m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n\n model_weights.token as `maybe_model_weights_token: tokens::tokens::model_weights::ModelWeightToken`,\n model_weights.title as maybe_model_weights_title,\n model_weights.weights_type as `maybe_model_weights_type: enums::by_table::model_weights::weights_types::WeightsType`,\n model_weights.weights_category as `maybe_model_weights_category: enums::by_table::model_weights::weights_category::WeightsCategory`,\n\n model_weight_cover_image.public_bucket_directory_hash as maybe_model_cover_image_public_bucket_hash,\n model_weight_cover_image.maybe_public_bucket_prefix as maybe_model_cover_image_public_bucket_prefix,\n model_weight_cover_image.maybe_public_bucket_extension as maybe_model_cover_image_public_bucket_extension,\n\n model_weight_creator.token as `maybe_model_weight_creator_user_token: tokens::tokens::users::UserToken`,\n model_weight_creator.username as maybe_model_weight_creator_username,\n model_weight_creator.display_name as maybe_model_weight_creator_display_name,\n model_weight_creator.email_gravatar_hash as maybe_model_weight_creator_gravatar_hash,\n\n m.public_bucket_directory_hash,\n m.maybe_public_bucket_prefix,\n m.maybe_public_bucket_extension,\n\n entity_stats.ratings_positive_count as maybe_ratings_positive_count,\n entity_stats.ratings_negative_count as maybe_ratings_negative_count,\n entity_stats.bookmark_count as maybe_bookmark_count,\n\n featured_items.entity_token IS NOT NULL AS is_featured,\n\n m.created_at,\n m.updated_at\n\nFROM media_files AS m\nLEFT OUTER JOIN users\n ON m.maybe_creator_user_token = users.token\nLEFT OUTER JOIN model_weights\n ON m.maybe_origin_model_token = model_weights.token\nLEFT OUTER JOIN media_files as media_file_cover_image\n ON media_file_cover_image.token = m.maybe_cover_image_media_file_token\nLEFT OUTER JOIN media_files as model_weight_cover_image\n ON model_weight_cover_image.token = model_weights.maybe_cover_image_media_file_token\nLEFT OUTER JOIN users as model_weight_creator\n ON model_weight_creator.token = model_weights.creator_user_token\nLEFT OUTER JOIN entity_stats\n ON entity_stats.entity_type = \"media_file\"\n AND entity_stats.entity_token = m.token\nLEFT OUTER JOIN prompts\n ON prompts.token = m.maybe_prompt_token\nLEFT OUTER JOIN featured_items\n ON featured_items.entity_type = 'media_file'\n AND featured_items.entity_token = m.token\n AND featured_items.deleted_at IS NULL\nWHERE\n m.token = ?\n ",
"describe": {
"columns": [
{
@@ -65,16 +65,6 @@
},
{
"ordinal": 6,
"name": "maybe_mime_type",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 7,
"name": "maybe_creator_user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
@@ -84,7 +74,7 @@
}
},
{
"ordinal": 8,
"ordinal": 7,
"name": "maybe_creator_username",
"type_info": {
"type": "VarString",
@@ -94,7 +84,7 @@
}
},
{
"ordinal": 9,
"ordinal": 8,
"name": "maybe_creator_display_name",
"type_info": {
"type": "VarString",
@@ -104,7 +94,7 @@
}
},
{
"ordinal": 10,
"ordinal": 9,
"name": "maybe_creator_gravatar_hash",
"type_info": {
"type": "String",
@@ -114,7 +104,7 @@
}
},
{
"ordinal": 11,
"ordinal": 10,
"name": "maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken",
"type_info": {
"type": "VarString",
@@ -124,7 +114,7 @@
}
},
{
"ordinal": 12,
"ordinal": 11,
"name": "maybe_batch_token: tokens::tokens::batch_generations::BatchGenerationToken",
"type_info": {
"type": "VarString",
@@ -134,7 +124,7 @@
}
},
{
"ordinal": 13,
"ordinal": 12,
"name": "maybe_style_transfer_source_media_file_token: tokens::tokens::media_files::MediaFileToken",
"type_info": {
"type": "VarString",
@@ -144,7 +134,7 @@
}
},
{
"ordinal": 14,
"ordinal": 13,
"name": "maybe_scene_source_media_file_token: tokens::tokens::media_files::MediaFileToken",
"type_info": {
"type": "VarString",
@@ -154,7 +144,7 @@
}
},
{
"ordinal": 15,
"ordinal": 14,
"name": "maybe_title",
"type_info": {
"type": "VarString",
@@ -164,7 +154,7 @@
}
},
{
"ordinal": 16,
"ordinal": 15,
"name": "maybe_text_transcript",
"type_info": {
"type": "Blob",
@@ -174,7 +164,7 @@
}
},
{
"ordinal": 17,
"ordinal": 16,
"name": "maybe_origin_filename",
"type_info": {
"type": "VarString",
@@ -184,7 +174,7 @@
}
},
{
"ordinal": 18,
"ordinal": 17,
"name": "maybe_duration_millis",
"type_info": {
"type": "Long",
@@ -194,7 +184,7 @@
}
},
{
"ordinal": 19,
"ordinal": 18,
"name": "maybe_prompt_token: tokens::tokens::prompts::PromptToken",
"type_info": {
"type": "VarString",
@@ -204,7 +194,7 @@
}
},
{
"ordinal": 20,
"ordinal": 19,
"name": "maybe_other_prompt_args",
"type_info": {
"type": "Blob",
@@ -214,7 +204,7 @@
}
},
{
"ordinal": 21,
"ordinal": 20,
"name": "maybe_file_cover_image_public_bucket_hash",
"type_info": {
"type": "VarString",
@@ -224,7 +214,7 @@
}
},
{
"ordinal": 22,
"ordinal": 21,
"name": "maybe_file_cover_image_public_bucket_prefix",
"type_info": {
"type": "VarString",
@@ -234,7 +224,7 @@
}
},
{
"ordinal": 23,
"ordinal": 22,
"name": "maybe_file_cover_image_public_bucket_extension",
"type_info": {
"type": "VarString",
@@ -244,7 +234,7 @@
}
},
{
"ordinal": 24,
"ordinal": 23,
"name": "creator_set_visibility: enums::common::visibility::Visibility",
"type_info": {
"type": "String",
@@ -254,7 +244,7 @@
}
},
{
"ordinal": 25,
"ordinal": 24,
"name": "maybe_model_weights_token: tokens::tokens::model_weights::ModelWeightToken",
"type_info": {
"type": "VarString",
@@ -264,7 +254,7 @@
}
},
{
"ordinal": 26,
"ordinal": 25,
"name": "maybe_model_weights_title",
"type_info": {
"type": "VarString",
@@ -274,7 +264,7 @@
}
},
{
"ordinal": 27,
"ordinal": 26,
"name": "maybe_model_weights_type: enums::by_table::model_weights::weights_types::WeightsType",
"type_info": {
"type": "VarString",
@@ -284,7 +274,7 @@
}
},
{
"ordinal": 28,
"ordinal": 27,
"name": "maybe_model_weights_category: enums::by_table::model_weights::weights_category::WeightsCategory",
"type_info": {
"type": "VarString",
@@ -294,7 +284,7 @@
}
},
{
"ordinal": 29,
"ordinal": 28,
"name": "maybe_model_cover_image_public_bucket_hash",
"type_info": {
"type": "VarString",
@@ -304,7 +294,7 @@
}
},
{
"ordinal": 30,
"ordinal": 29,
"name": "maybe_model_cover_image_public_bucket_prefix",
"type_info": {
"type": "VarString",
@@ -314,7 +304,7 @@
}
},
{
"ordinal": 31,
"ordinal": 30,
"name": "maybe_model_cover_image_public_bucket_extension",
"type_info": {
"type": "VarString",
@@ -324,7 +314,7 @@
}
},
{
"ordinal": 32,
"ordinal": 31,
"name": "maybe_model_weight_creator_user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
@@ -334,7 +324,7 @@
}
},
{
"ordinal": 33,
"ordinal": 32,
"name": "maybe_model_weight_creator_username",
"type_info": {
"type": "VarString",
@@ -344,7 +334,7 @@
}
},
{
"ordinal": 34,
"ordinal": 33,
"name": "maybe_model_weight_creator_display_name",
"type_info": {
"type": "VarString",
@@ -354,7 +344,7 @@
}
},
{
"ordinal": 35,
"ordinal": 34,
"name": "maybe_model_weight_creator_gravatar_hash",
"type_info": {
"type": "String",
@@ -364,7 +354,7 @@
}
},
{
"ordinal": 36,
"ordinal": 35,
"name": "public_bucket_directory_hash",
"type_info": {
"type": "VarString",
@@ -374,7 +364,7 @@
}
},
{
"ordinal": 37,
"ordinal": 36,
"name": "maybe_public_bucket_prefix",
"type_info": {
"type": "VarString",
@@ -384,7 +374,7 @@
}
},
{
"ordinal": 38,
"ordinal": 37,
"name": "maybe_public_bucket_extension",
"type_info": {
"type": "VarString",
@@ -394,7 +384,7 @@
}
},
{
"ordinal": 39,
"ordinal": 38,
"name": "maybe_ratings_positive_count",
"type_info": {
"type": "Long",
@@ -404,7 +394,7 @@
}
},
{
"ordinal": 40,
"ordinal": 39,
"name": "maybe_ratings_negative_count",
"type_info": {
"type": "Long",
@@ -414,7 +404,7 @@
}
},
{
"ordinal": 41,
"ordinal": 40,
"name": "maybe_bookmark_count",
"type_info": {
"type": "Long",
@@ -424,7 +414,7 @@
}
},
{
"ordinal": 42,
"ordinal": 41,
"name": "is_featured",
"type_info": {
"type": "LongLong",
@@ -434,7 +424,7 @@
}
},
{
"ordinal": 43,
"ordinal": 42,
"name": "created_at",
"type_info": {
"type": "Timestamp",
@@ -444,7 +434,7 @@
}
},
{
"ordinal": 44,
"ordinal": 43,
"name": "updated_at",
"type_info": {
"type": "Timestamp",
@@ -481,7 +471,6 @@
true,
true,
true,
true,
false,
true,
true,
@@ -505,5 +494,5 @@
false
]
},
"hash": "58cb3fd03fb215b1d323f6aa6a5fd713900b9d931700c4226a1d9713b5e469c7"
"hash": "bfbe17ad0d993033c1015881f679fc27f5f16fb3edb24e30295c8188ff63d5cd"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n token as `token: tokens::tokens::users::UserToken`,\n username,\n email_address,\n password_hash as `password_hash: crate::queries::users::user::lookup_user_for_login_result::VecBytes`,\n password_version,\n is_banned,\n maybe_feature_flags\nFROM users\nWHERE email_address = ?\nLIMIT 1\n ",
"query": "\nSELECT\n token as `token: tokens::tokens::users::UserToken`,\n username,\n email_address,\n password_hash as `password_hash: crate::queries::users::user::lookup_user_for_login_result::VecBytes`,\n password_version,\n is_banned\nFROM users\nWHERE email_address = ?\nLIMIT 1\n ",
"describe": {
"columns": [
{
@@ -62,16 +62,6 @@
"char_set": 63,
"max_size": 1
}
},
{
"ordinal": 6,
"name": "maybe_feature_flags",
"type_info": {
"type": "VarString",
"flags": "BINARY",
"char_set": 224,
"max_size": 1020
}
}
],
"parameters": {
@@ -83,9 +73,8 @@
false,
false,
false,
false,
true
false
]
},
"hash": "48889cc9c9b8dbe482e74687629a6958c1c9bf8a1cd5ae177afea0ad0709736d"
"hash": "e8705ee9b3bc3213e806946fef651e9bb8c2cb5cc2ac8eff3eaed82d9a88f90e"
}
Generated
+1300 -1506
View File
File diff suppressed because it is too large Load Diff
+4 -2
View File
@@ -7,7 +7,6 @@ members = [
"crates/_types/buckets",
"crates/_types/composite_identifiers",
"crates/_types/enums",
"crates/_types/enums_public",
"crates/_types/reusable_types", # TODO(bt, 2022-12-21): Deprecate
"crates/_types/tokens",
@@ -28,6 +27,7 @@ members = [
"crates/lib/api_clients/cloud_storage",
"crates/lib/api_clients/google_drive_common",
"crates/lib/api_clients/thumbnail_generator",
"crates/lib/api_clients/twitch_common",
"crates/lib/bootstrap",
"crates/lib/caching/memory_caching",
"crates/lib/caching/redis_caching",
@@ -60,7 +60,6 @@ members = [
"crates/lib/password",
"crates/lib/prompt_engineering",
"crates/lib/redis_common",
"crates/lib/redis_schema",
"crates/lib/server_environment",
"crates/lib/sqlx_mysql_helpers",
"crates/lib/storyteller_root",
@@ -83,13 +82,16 @@ members = [
"crates/service/job/tts_inference_job",
"crates/service/job/w2l_download_job",
"crates/service/job/w2l_inference_job",
"crates/service/other/twitch_pubsub_subscriber",
"crates/service/web/dummy_service",
"crates/service/web/storyteller_web",
"crates/service/web/websocket_gateway",
# Service Plugins: complete subsystems that can be plugged into services (users, billing, etc.)
"crates/service/plugins/billing",
"crates/service/plugins/inference_workloads",
"crates/service/plugins/user_traits_component",
"crates/service/plugins/users",
# Vendored code
"crates/vendor/limitation",
-47
View File
@@ -1,47 +0,0 @@
# -*- mode: Python -*-
if k8s_context() != 'black1':
fail("failing early to avoid overwriting prod")
local_resource('storyteller-web-binary',
cmd='SQLX_OFFLINE=true cargo build --release --bin storyteller-web --target=x86_64-unknown-linux-musl',
)
allow_k8s_contexts('black1')
sync_src_crates = sync('./crates', '/storyteller-rust/source/crates')
sync_src_sql = sync('./_sql', '/storyteller-rust/source/_sql')
sync_src_cargo_lock = sync('./Cargo.lock', '/storyteller-rust/source/Cargo.lock')
sync_src_cargo = sync('./Cargo.toml', '/storyteller-rust/source/Cargo.toml')
sync_src_env = sync('./.env', '/storyteller-rust/source/.env')
sync_src_startup = sync('./_develop/localdev/startup.sh', '/storyteller-rust/_develop/localdev/startup.sh')
sync_src_storyteller_binary = sync('./target/x86_64-unknown-linux-musl/release/storyteller-web', '/storyteller-rust/target/x86_64-unknown-linux-musl/release/storyteller-web')
docker_build('storyteller-web',
'.',
dockerfile='_develop/localdev/Dockerfile',
platform='linux/amd64',
only=[
'./target/x86_64-unknown-linux-musl/release/storyteller-web',
'_develop/localdev/Dockerfile',
'./_develop/localdev/startup.sh',
'crates/service/web/storyteller_web/config/storyteller-web.development.env',
'_sql', 'diesel.toml', '.env'],
live_update = [
sync_src_crates,
sync_src_sql,
sync_src_cargo_lock,
sync_src_cargo,
sync_src_env,
sync_src_startup,
sync_src_storyteller_binary,
run('date > /restart.txt')
],
)
k8s_yaml('_develop/localdev/kubernetes.yml',
)
k8s_resource(
objects=['ssd-hostpath:storageclass', 'test-pvc:persistentvolumeclaim'],
new_name='storyteller-web-pvc',
trigger_mode=TRIGGER_MODE_MANUAL,
)
k8s_resource('mysql', port_forwards='3307:3306')
k8s_resource('storyteller-web', port_forwards=12345)
+1 -12
View File
@@ -6,20 +6,9 @@ RUN apt-get update && \
apt-get install -y \
mariadb-client \
imagemagick \
python3-pip \
libmariadb-dev \
&& rm -rf /var/lib/apt/lists/*
RUN cd /tmp && \
wget -O entr.zip https://github.com/eradman/entr/archive/refs/tags/5.6.zip && \
unzip entr.zip && \
cd entr-5.6 && \
./configure && \
make test && \
make install \
&& rm -rf /tmp/entr-5.6
RUN cargo install diesel_cli --features mysql && \
cargo install sqlx-cli --features mysql
@@ -30,5 +19,5 @@ ARG GIT_SHA
RUN echo -n ${GIT_SHA} > /GIT_SHA
CMD ["/storyteller-rust/_develop/localdev/startup.sh"]
CMD ["/storyteller-rust/localdev/startup.sh"]
+15 -79
View File
@@ -27,38 +27,6 @@ spec:
ports:
- containerPort: 3306
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ssd-hostpath
provisioner: microk8s.io/hostpath
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
parameters:
pvDir: /home/kasisnu/microk8s/hostpath-provisioner
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: test-pvc
spec:
storageClassName: ssd-hostpath
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: ConfigMap
metadata:
name: storyteller-config
data:
buckets.vocodes_bucket_region: us-east1
buckets.vocodes_private_bucket_name: kasisnu-test-area-private
buckets.vocodes_public_bucket_name: kasisnu-test-area-public
---
apiVersion: apps/v1
@@ -97,57 +65,29 @@ spec:
labels:
app: storyteller-web
spec:
runtimeClassName: nvidia
containers:
- name: gpt-sovitsdir
image: localhost:32000/some-gpt-sovits:latest
imagePullPolicy: Always
command: ["sleep", "infinity"]
resources:
limits:
nvidia.com/gpu: "1"
requests:
nvidia.com/gpu: "1"
volumeMounts:
- mountPath: /application-dir
name: application-dir
- name: storyteller-web
image: storyteller-web:latest
imagePullPolicy: Always
imagePullPolicy: Never
env:
- name: MYSQL_URL
value: mysql://storyteller:password@mysql/storyteller
value: mysql://storyteller:password@mysql.default/storyteller
- name: REDIS_0_URL
value: redis://redisd@redis/0
value: redis://redisd@redis.default/0
- name: REDIS_1_URL
value: redis://redisd@redis/1
value: redis://redisd@redis.default/1
- name: ACCESS_KEY
valueFrom:
secretKeyRef:
name: bucket-secrets
key: access-key
value: garbage
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: bucket-secrets
key: secret-key
value: garbage
- name: REGION_NAME
valueFrom:
configMapKeyRef:
name: storyteller-config
key: buckets.vocodes_bucket_region
value: garbage
- name: W2L_PRIVATE_DOWNLOAD_BUCKET_NAME
valueFrom:
configMapKeyRef:
name: storyteller-config
key: buckets.vocodes_private_bucket_name
value: garbage
- name: W2L_PUBLIC_DOWNLOAD_BUCKET_NAME
valueFrom:
configMapKeyRef:
name: storyteller-config
key: buckets.vocodes_public_bucket_name
value: garbage
- name: AUDIO_UPLOADS_BUCKET_ROOT
value: /user_uploaded_w2l_audio
value: garbage
- name: TWITCH_USERNAME
value: garbage
- name: TWITCH_STREAM_KEY
@@ -161,14 +101,10 @@ spec:
volumeMounts:
- mountPath: /storyteller-rust
name: storyteller-rust-folder
- mountPath: /application-dir
name: application-dir
volumes:
- name: storyteller-rust-folder
persistentVolumeClaim:
claimName: test-pvc
- name: application-dir
emptyDir: {}
hostPath:
path: /storyteller-rust
---
apiVersion: v1
@@ -180,7 +116,7 @@ spec:
ports:
- name: mysql
port: 3306
nodePort: 32206
nodePort: 3306
selector:
app: mysql
@@ -195,7 +131,7 @@ spec:
ports:
- name: redis
port: 6379
nodePort: 32379
nodePort: 6379
selector:
app: redis
@@ -210,7 +146,7 @@ spec:
ports:
- name: storyteller-web
port: 12345
nodePort: 32345
nodePort: 12345
selector:
app: storyteller-web
+2 -5
View File
@@ -2,8 +2,5 @@
set +e +o pipefail
diesel migration run --database-url="${MYSQL_URL}"
set -e -o pipefail
echo 'hello world in startup.sh'
#sleep infinity
date > /restart.txt
echo /restart.txt | entr -nrz /storyteller-rust/target/x86_64-unknown-linux-musl/release/storyteller-web
#/storyteller-rust/target/x86_64-unknown-linux-musl/release/storyteller-web
./target/x86_64-unknown-linux-musl/release/storyteller-web
@@ -1,53 +0,0 @@
{
"settings" : {
"index.number_of_shards" : 3,
"index.number_of_replicas" : 2
},
"mappings" : {
"properties" : {
"token" : { "type" : "keyword" },
"media_class" : { "type" : "keyword" },
"media_type" : { "type" : "keyword" },
"maybe_media_subtype" : { "type" : "keyword" },
"maybe_engine_category" : { "type" : "keyword" },
"maybe_animation_type" : { "type" : "keyword" },
"maybe_mime_type" : { "type" : "keyword" },
"public_bucket_hash" : { "type" : "keyword" },
"public_bucket_prefix" : { "type" : "keyword" },
"public_bucket_extension" : { "type" : "keyword" },
"creator_set_visibility" : { "type" : "keyword" },
"maybe_title" : { "type" : "search_as_you_type" },
"maybe_title_as_keyword" : { "type" : "keyword" },
"maybe_cover_image_media_file_token" : { "type" : "keyword" },
"maybe_cover_image_public_bucket_hash" : { "type" : "keyword" },
"maybe_cover_image_public_bucket_prefix" : { "type" : "keyword" },
"maybe_cover_image_public_bucket_extension" : { "type" : "keyword" },
"maybe_creator_user_token" : { "type" : "keyword" },
"maybe_creator_username" : { "type" : "keyword" },
"maybe_creator_display_name" : { "type" : "text" },
"maybe_creator_gravatar_hash" : { "type" : "text" },
"created_at" : { "type" : "date" },
"updated_at" : { "type" : "date" },
"user_deleted_at" : { "type" : "date" },
"mod_deleted_at" : { "type" : "date" },
"is_featured" : {
"type": "boolean",
"index": true
},
"is_deleted" : {
"type": "boolean",
"index": true
}
}
}
}
@@ -13,7 +13,6 @@
"weights_category" : { "type" : "keyword" },
"title" : { "type" : "search_as_you_type" },
"title_as_keyword" : { "type" : "keyword" },
"maybe_cover_image_media_file_token" : { "type" : "keyword" },
"maybe_cover_image_public_bucket_hash" : { "type" : "keyword" },
@@ -1,67 +0,0 @@
{
"query": {
"bool": {
"must": [
{
"bool": {
"must" : [
{
"term": {
"is_deleted": false
}
},
{
"term": {
"media_class": "dimensional"
}
},
{
"term": {
"media_type": "glb"
}
},
{
"bool": {
"should": [
{ "term": { "maybe_engine_category": "object"} },
{ "term": { "maybe_engine_category": "image_plane"} }
]
}
}
],
"should": [
{
"fuzzy": {
"maybe_title": {
"value": "REPLACE_ME",
"fuzziness": 2
}
}
},
{
"match": {
"maybe_title": {
"query": "REPLACE_ME",
"boost": 1
}
}
},
{
"multi_match": {
"query": "REPLACE_ME",
"type": "bool_prefix",
"fields": [
"maybe_title",
"maybe_title._2gram",
"maybe_title._3gram"
],
"boost": 50
}
}
]
}
}
]
}
}
}
@@ -1,42 +0,0 @@
{
"query": {
"bool": {
"must": [
{
"bool": {
"should": [
{
"fuzzy": {
"title": {
"value": "REPLACE_ME",
"fuzziness": 2
}
}
},
{
"match": {
"title": {
"query": "REPLACE_ME",
"boost": 1
}
}
},
{
"multi_match": {
"query": "REPLACE_ME",
"type": "bool_prefix",
"fields": [
"title",
"title._2gram",
"title._3gram"
],
"boost": 50
}
}
]
}
}
]
}
}
}
@@ -19,29 +19,3 @@ where
and m.mod_deleted_at IS NULL
order by m.title asc;
-- Top models within time period
-- NB: id threshold is to avoid a full table scan
select
mw.token,
mw.title,
mw.weights_type,
mw.weights_category,
u.username as creator_username,
r.use_count,
mw.created_at,
mw.user_deleted_at,
mw.mod_deleted_at
from (
select maybe_model_token, count(*) as use_count
from generic_inference_jobs
where maybe_model_token IS NOT NULL
and created_at > NOW() - INTERVAL 1 DAY
and id > 40000000
group by maybe_model_token
) as r
join model_weights as mw
on mw.token = r.maybe_model_token
join users as u
on u.token = mw.creator_user_token
order by r.use_count desc
limit 100;
@@ -94,7 +94,6 @@ SET
can_access_studio = false,
maybe_feature_flags = 'studio,video_style_transfer'
WHERE username IN (
'FoxtailStudio',
'chechisauri0',
'dakrid',
'omega7321',
@@ -1,29 +0,0 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
select *
from generic_inference_jobs
where job_type = 'comfy_ui'
and status NOT IN (
'pending',
'started',
'complete_success'
)
order by id desc
limit 5\G
-- List started jobs
select *
from generic_inference_jobs
where job_type = 'comfy_ui'
and status IN ('started')
order by id desc
limit 50\G
update generic_inference_jobs
set inference_category = 'convert_bvh_to_workflow'
where inference_category = 'deprecated_field'
limit 5;
[2024-07-16T22:53:07Z ERROR inference_job::job::job_loop::main_loop] Error querying jobs: error: ""
@@ -6,8 +6,3 @@
select username from users where maybe_feature_flags like '%studio%';
select username from users where can_access_studio = true;
-- Who signed up recently and has access?
select username, maybe_source
from users where can_access_studio = true
and created_at > NOW() - interval 20 minute;
@@ -11,72 +11,8 @@ from media_files as m
join users as u
on m.maybe_creator_user_token = u.token
where u.can_access_studio = true
and m.created_at > (CURDATE() - INTERVAL 14 DAY)
and m.created_at > (CURDATE() - INTERVAL 1 WEEK)
and m.is_intermediate_system_file = false
and m.media_class = 'video'
group by u.token
order by video_count desc;
-- Without internal users
select
u.username,
count(*) as video_count,
u.maybe_source,
u.created_at
from media_files as m
join users as u
on m.maybe_creator_user_token = u.token
where u.maybe_feature_flags LIKE '%studio%'
and m.created_at > (CURDATE() - INTERVAL 1 DAY)
and m.is_intermediate_system_file = false
and m.media_class = 'video'
and u.username NOT IN (
'bflat',
'candyfoxxx',
'crossproduct1',
'echelon',
'endtimes',
'heart_ribbon',
'kasisnu',
'mechacosm',
'moonchamp',
'olivicmic',
'printrman',
'vegito1089',
'wilwong',
'yae_ph'
)
group by u.token
order by created_at desc;
-- See videos generated
-- and m.is_intermediate_system_file IS TRUE
-- where u.maybe_feature_flags LIKE '%studio%'
select
u.username,
u.maybe_source,
m.token,
m.is_intermediate_system_file as sys_file,
m.created_at
from media_files as m
join users as u
on m.maybe_creator_user_token = u.token
WHERE m.created_at > (CURDATE() - INTERVAL 1 DAY)
and m.media_class = 'video'
and u.username NOT IN (
'bflat',
'candyfoxxx',
'crossproduct1',
'echelon',
'endtimes',
'heart_ribbon',
'kasisnu',
'mechacosm',
'moonchamp',
'olivicmic',
'printrman',
'vegito1089',
'wilwong',
'yae_ph'
)
order by m.created_at desc;
@@ -1,17 +0,0 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- Enroll internal accounts in studio + loyalty features
UPDATE users
SET
can_access_studio = true,
maybe_feature_flags = 'explore_media,studio,video_style_transfer',
maybe_loyalty_program_key = 'fakeyou_contributor',
user_role_slug = "admin"
WHERE username IN (
'48hourfilm',
'intro_video',
'zzz_last_item'
)
LIMIT 5;
@@ -199,7 +199,7 @@ CREATE TABLE generic_inference_jobs (
attempt_count SMALLINT UNSIGNED NOT NULL DEFAULT 0,
-- If the user chooses to no longer display or poll the job, we mark this flag true.
-- This should only be set for terminal state (completed or failed) jobs.
-- This can only be set for terminal state (completed or failed) jobs.
-- We use this flag to remove the job from the user's view.
is_dismissed_by_user BOOLEAN NOT NULL DEFAULT FALSE,
+5 -15
View File
@@ -6,12 +6,6 @@
-- noinspection SqlResolveForFile
-- This table contains both user uploads and inference or processing results.
--
-- The fields in media_files that should be filterable are:
-- * media_class ("audio", "video", "image", "dimensional")
-- * media_type ("fbx", "glb", "scene_json", "png", "jpg", etc.)
-- * engine_category ("scene", "character", "animation", "object", "image_plane", etc.)
--
CREATE TABLE media_files (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
@@ -115,11 +109,6 @@ CREATE TABLE media_files (
-- * `glb` for GLB files
-- * `gltf` for GLTF files
-- * `scene_ron` for SCN.RON files
-- * `scene_json` for scene files for three.js
-- * `pmd`
-- * `vmd`
-- * `pmx`
-- * `csv`
media_type VARCHAR(16) NOT NULL,
-- Broad class of media:
@@ -128,15 +117,16 @@ CREATE TABLE media_files (
-- * 'audio' for wav, mp3, etc.
-- * 'image' for a variety of video types.
-- * 'video' for a variety of video types.
-- * 'dimensional' for a variety of 3d types
-- * 'animation' for animations (engine)
-- * 'character' for characters (engine)
-- * 'prop' for props (engine)
-- * 'scene' for scenes (engine; internal and external scenes)
media_class VARCHAR(16) NOT NULL DEFAULT "unknown",
-- A media file's possible subtype. Typically used for Storyteller Studio.
-- * 'mixamo' for mixamo animations (eg. for BVH, GLB, FBX, etc. files)
-- * 'mocap_net' for mocapnet animations (eg. for BVH files)
-- * 'scene'
-- * 'character'
-- * 'animation'
-- * 'scene' for generic scenes (eg. for BVH, GLB, FBX, etc. files)
maybe_media_subtype VARCHAR(32) DEFAULT NULL,
-- The file's mime type.
+21 -26
View File
@@ -1,34 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="description" content="SwaggerUI" />
<title>Storyteller API</title>
<link rel="stylesheet" href="//unpkg.com/swagger-ui-dist@5.11.0/swagger-ui.css" />
</head>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="//unpkg.com/swagger-ui-dist@3/swagger-ui.css">
<title>Storyteller API</title>
<body>
<div id="app-docs" />
<div id="swagger-ui"></div>
<script src="//unpkg.com/swagger-ui-dist@3/swagger-ui-bundle.js"></script>
<script>
<script src="//unpkg.com/swagger-ui-dist@5.11.0/swagger-ui-bundle.js" crossorigin></script>
<script src="//unpkg.com/swagger-ui-dist@5.11.0/swagger-ui-standalone-preset.js" crossorigin></script>
<script>
window.onload = () => {
window.ui = SwaggerUIBundle({
url: 'api.json',
dom_id: '#swagger-ui',
window.onload = function () {
const ui = SwaggerUIBundle({
url: "api.json",
dom_id: "#app-docs",
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
layout: "StandaloneLayout",
tagsSorter: 'alpha', // Sort tags (endpoint groupings) alphabetically
defaultModelRendering: 'model', // Show model instead of "example request"
defaultModelExpandDepth: 6,
});
};
</script>
})
}
</script>
</body>
</html>
+2 -2
View File
@@ -34,8 +34,8 @@ utoipa = { version = "4", features = ["chrono","actix_extras"] }
[dev-dependencies]
# Enum utilities
strum = { version = "0.26", features = ["derive"] }
strum = { version = "0.25", features = ["derive"] }
# Test serialization
serde_json = "1.0.108"
toml = "0.8.14"
toml = "0.5.8"
@@ -16,50 +16,32 @@ use utoipa::ToSchema;
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize, Default, ToSchema)]
pub enum InferenceCategory {
/// Deprecate this field !!!
/// We should drain all jobs from using this database field, then remove it.
#[serde(rename = "deprecated_field")]
DeprecatedField,
/// Facial lipsync animation (eg. SadTalker, Wav2Lip)
/// Eg. SadTalker and possibly Wav2Lip
#[serde(rename = "lipsync_animation")]
#[default]
LipsyncAnimation,
/// FakeYou's text to speech (eg. Tacotron2)
#[serde(rename = "text_to_speech")]
TextToSpeech,
/// FakeYou's voice conversion (eg. svc, rvc)
#[serde(rename = "voice_conversion")]
VoiceConversion,
/// Image generation (eg. Stable Diffusion 1.5)
#[serde(rename = "image_generation")]
ImageGeneration,
/// Turn video into animation data with mocap processing (eg. Mocapnet).
#[serde(rename = "mocap")]
Mocap,
/// ComfyUI workflows
/// This is what powers Storyteller Studio!
#[serde(rename = "workflow")]
Workflow,
/// FBX to GLTF/GLB.
/// Still supported, but few people will use it.
#[serde(rename = "format_conversion")]
FormatConversion,
/// DEPRECATED. Do not use.
/// This was for ReRenderAVideo, which we never productionized.
#[deprecated(note = "This was for ReRenderAVideo, which we never productionized.")]
#[serde(rename = "video_filter")]
VideoFilter,
/// DEPRECATED. Bevy engine serverside rendering.
#[deprecated(note = "This was for Bevy engine's server side rendering.")]
#[serde(rename = "image_generation")]
ImageGeneration,
#[serde(rename = "mocap")]
Mocap,
#[serde(rename = "workflow")]
Workflow,
#[serde(rename = "format_conversion")]
FormatConversion,
#[serde(rename = "convert_bvh_to_workflow")]
ConvertBvhToWorkflow,
}
@@ -73,30 +55,28 @@ impl_mysql_from_row!(InferenceCategory);
impl InferenceCategory {
pub fn to_str(&self) -> &'static str {
match self {
Self::DeprecatedField => "deprecated_field",
Self::LipsyncAnimation => "lipsync_animation",
Self::TextToSpeech => "text_to_speech",
Self::VoiceConversion => "voice_conversion",
Self::VideoFilter => "video_filter",
Self::ImageGeneration => "image_generation",
Self::Mocap => "mocap",
Self::Workflow => "workflow",
Self::FormatConversion => "format_conversion",
Self::VideoFilter => "video_filter",
Self::ConvertBvhToWorkflow => "convert_bvh_to_workflow",
}
}
pub fn from_str(value: &str) -> Result<Self, String> {
match value {
"deprecated_field" => Ok(Self::DeprecatedField),
"lipsync_animation" => Ok(Self::LipsyncAnimation),
"text_to_speech" => Ok(Self::TextToSpeech),
"voice_conversion" => Ok(Self::VoiceConversion),
"video_filter" => Ok(Self::VideoFilter),
"image_generation" => Ok(Self::ImageGeneration),
"mocap" => Ok(Self::Mocap),
"workflow" => Ok(Self::Workflow),
"format_conversion" => Ok(Self::FormatConversion),
"video_filter" => Ok(Self::VideoFilter),
"convert_bvh_to_workflow" => Ok(Self::ConvertBvhToWorkflow),
_ => Err(format!("invalid value: {:?}", value)),
}
@@ -106,15 +86,14 @@ impl InferenceCategory {
// NB: BTreeSet is sorted
// NB: BTreeSet::from() isn't const, but not worth using LazyStatic, etc.
BTreeSet::from([
Self::DeprecatedField,
Self::LipsyncAnimation,
Self::TextToSpeech,
Self::VoiceConversion,
Self::VideoFilter,
Self::ImageGeneration,
Self::Mocap,
Self::Workflow,
Self::FormatConversion,
Self::VideoFilter,
Self::ConvertBvhToWorkflow,
])
}
@@ -130,43 +109,40 @@ mod tests {
#[test]
fn test_serialization() {
assert_serialization(InferenceCategory::DeprecatedField, "deprecated_field");
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");
assert_serialization(InferenceCategory::VideoFilter, "video_filter");
assert_serialization(InferenceCategory::ConvertBvhToWorkflow, "convert_bvh_to_workflow");
}
#[test]
fn to_str() {
assert_eq!(InferenceCategory::DeprecatedField.to_str(), "deprecated_field");
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");
assert_eq!(InferenceCategory::VideoFilter.to_str(), "video_filter");
assert_eq!(InferenceCategory::ConvertBvhToWorkflow.to_str(), "convert_bvh_to_workflow");
}
#[test]
fn from_str() {
assert_eq!(InferenceCategory::from_str("deprecated_field").unwrap(), InferenceCategory::DeprecatedField);
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);
assert_eq!(InferenceCategory::from_str("video_filter").unwrap(), InferenceCategory::VideoFilter);
assert_eq!(InferenceCategory::from_str("convert_bvh_to_workflow").unwrap(), InferenceCategory::ConvertBvhToWorkflow);
}
@@ -174,16 +150,15 @@ mod tests {
fn all_variants() {
// Static check
let mut variants = InferenceCategory::all_variants();
assert_eq!(variants.len(), 10);
assert_eq!(variants.pop_first(), Some(InferenceCategory::DeprecatedField));
assert_eq!(variants.len(), 9);
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(), Some(InferenceCategory::VideoFilter));
assert_eq!(variants.pop_first(), Some(InferenceCategory::ConvertBvhToWorkflow));
assert_eq!(variants.pop_first(), None);
@@ -15,21 +15,11 @@ use strum::EnumIter;
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum InferenceJobType {
/// Storyteller Studio and Video Style Transfer Jobs (which we may want to split).
/// These run in Comfy.
/// TODO(bt,2024-07-15): We may segregate these two job types in the future
VideoRender,
/// Live Portrait Jobs.
/// These run in Comfy.
LivePortrait,
/// Voice jobs that use GPT-Sovits
GptSovits,
/// Job that converts bevy to workflow files
#[serde(rename = "bevy_to_workflow")]
BevyToWorkflow,
/// Jobs that run ComfyUI workflows
/// This is actually just for Video Style Transfer and Storyteller Studio.
#[deprecated(note = "Use VideoRender instead.")]
ComfyUi,
/// A job that turns "FBX" game engine files into "GLTF" files (Bevy-compatible).
@@ -39,6 +29,10 @@ pub enum InferenceJobType {
/// 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,
@@ -64,18 +58,6 @@ pub enum InferenceJobType {
/// (i.e. when we backfill the database column and make it non-nullable)
#[default]
Unknown,
/// DEPRECATED. DO NOT USE.
/// Job that converts bevy to workflow files
#[deprecated(note = "This was for Bevy engine's server side rendering.")]
#[serde(rename = "bevy_to_workflow")]
BevyToWorkflow,
#[deprecated(note = "This was for ReRenderAVideo, which we never productionized.")]
/// DEPRECATED. DO NOT USE.
/// Re-render a video is a video style transfer algorithm. We developed code
/// around it, but chose to develop AnimateDiff / ComfyUI support instead.
RerenderAVideo,
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
@@ -86,12 +68,11 @@ impl_mysql_enum_coders!(InferenceJobType);
impl InferenceJobType {
pub fn to_str(&self) -> &'static str {
match self {
Self::VideoRender => "video_render",
Self::LivePortrait => "live_portrait",
Self::GptSovits => "gpt_sovits",
Self::BevyToWorkflow => "bevy_to_workflow",
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",
@@ -99,19 +80,16 @@ impl InferenceJobType {
Self::StyleTTS2 => "styletts2",
Self::Tacotron2 => "tacotron2",
Self::Unknown => "unknown",
Self::BevyToWorkflow => "bevy_to_workflow",
Self::RerenderAVideo => "rerender_a_video",
}
}
pub fn from_str(value: &str) -> Result<Self, String> {
match value {
"video_render" => Ok(Self::VideoRender),
"live_portrait" => Ok(Self::LivePortrait),
"gpt_sovits" => Ok(Self::GptSovits),
"bevy_to_workflow" => Ok(Self::BevyToWorkflow),
"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),
@@ -119,8 +97,6 @@ impl InferenceJobType {
"styletts2" => Ok(Self::StyleTTS2),
"tacotron2" => Ok(Self::Tacotron2),
"unknown" => Ok(Self::Unknown),
"bevy_to_workflow" => Ok(Self::BevyToWorkflow),
"rerender_a_video" => Ok(Self::RerenderAVideo),
_ => Err(format!("invalid value: {:?}", value)),
}
}
@@ -129,12 +105,11 @@ impl InferenceJobType {
// NB: BTreeSet is sorted
// NB: BTreeSet::from() isn't const, but not worth using LazyStatic, etc.
BTreeSet::from([
Self::VideoRender,
Self::LivePortrait,
Self::GptSovits,
Self::BevyToWorkflow,
Self::ComfyUi,
Self::ConvertFbxToGltf,
Self::MocapNet,
Self::RerenderAVideo,
Self::RvcV2,
Self::SadTalker,
Self::SoVitsSvc,
@@ -142,8 +117,6 @@ impl InferenceJobType {
Self::StyleTTS2,
Self::Tacotron2,
Self::Unknown,
Self::BevyToWorkflow,
Self::RerenderAVideo,
])
}
}
@@ -163,12 +136,11 @@ mod tests {
#[test]
fn test_serialization() {
assert_serialization(InferenceJobType::VideoRender, "video_render");
assert_serialization(InferenceJobType::LivePortrait, "live_portrait");
assert_serialization(InferenceJobType::GptSovits, "gpt_sovits");
assert_serialization(InferenceJobType::BevyToWorkflow, "bevy_to_workflow");
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");
@@ -176,18 +148,15 @@ mod tests {
assert_serialization(InferenceJobType::StyleTTS2, "styletts2");
assert_serialization(InferenceJobType::Tacotron2, "tacotron2");
assert_serialization(InferenceJobType::Unknown, "unknown");
assert_serialization(InferenceJobType::BevyToWorkflow, "bevy_to_workflow");
assert_serialization(InferenceJobType::RerenderAVideo, "rerender_a_video");
}
#[test]
fn to_str() {
assert_eq!(InferenceJobType::VideoRender.to_str(), "video_render");
assert_eq!(InferenceJobType::LivePortrait.to_str(), "live_portrait");
assert_eq!(InferenceJobType::GptSovits.to_str(), "gpt_sovits");
assert_eq!(InferenceJobType::BevyToWorkflow.to_str(), "bevy_to_workflow");
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");
@@ -195,18 +164,15 @@ mod tests {
assert_eq!(InferenceJobType::StyleTTS2.to_str(), "styletts2");
assert_eq!(InferenceJobType::Tacotron2.to_str(), "tacotron2");
assert_eq!(InferenceJobType::Unknown.to_str(), "unknown");
assert_eq!(InferenceJobType::BevyToWorkflow.to_str(), "bevy_to_workflow");
assert_eq!(InferenceJobType::RerenderAVideo.to_str(), "rerender_a_video");
}
#[test]
fn from_str() {
assert_eq!(InferenceJobType::from_str("video_render").unwrap(), InferenceJobType::VideoRender);
assert_eq!(InferenceJobType::from_str("live_portrait").unwrap(), InferenceJobType::LivePortrait);
assert_eq!(InferenceJobType::from_str("gpt_sovits").unwrap(), InferenceJobType::GptSovits);
assert_eq!(InferenceJobType::from_str("bevy_to_workflow").unwrap(), InferenceJobType::BevyToWorkflow);
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);
@@ -214,21 +180,18 @@ mod tests {
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);
assert_eq!(InferenceJobType::from_str("bevy_to_workflow").unwrap(), InferenceJobType::BevyToWorkflow);
assert_eq!(InferenceJobType::from_str("rerender_a_video").unwrap(), InferenceJobType::RerenderAVideo);
}
#[test]
fn all_variants() {
// Static check
let mut variants = InferenceJobType::all_variants();
assert_eq!(variants.len(), 15);
assert_eq!(variants.pop_first(), Some(InferenceJobType::VideoRender));
assert_eq!(variants.pop_first(), Some(InferenceJobType::LivePortrait));
assert_eq!(variants.pop_first(), Some(InferenceJobType::GptSovits));
assert_eq!(variants.len(), 12);
assert_eq!(variants.pop_first(), Some(InferenceJobType::BevyToWorkflow));
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));
@@ -236,8 +199,6 @@ mod tests {
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(), Some(InferenceJobType::BevyToWorkflow));
assert_eq!(variants.pop_first(), Some(InferenceJobType::RerenderAVideo));
assert_eq!(variants.pop_first(), None);
// Generated check
@@ -17,10 +17,6 @@ use strum::EnumIter;
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
pub enum InferenceModelType {
// TODO(bt,2024-07-15): This is too generic. We probably need "StorytellerStudio", "LivePortrait", etc.
#[serde(rename = "comfy_ui")]
ComfyUi,
#[serde(rename = "rvc_v2")]
RvcV2,
// NB: sad_talker does use user-supplied models, so there is no "model token"
@@ -41,6 +37,8 @@ pub enum InferenceModelType {
StableDiffusion,
#[serde(rename = "mocap_net")]
MocapNet,
#[serde(rename = "comfy_ui")]
ComfyUi,
#[serde(rename = "styletts2")]
StyleTTS2,
/// A job that turns "FBX" game engine files into "GLTF" files (Bevy-compatible).
@@ -21,39 +21,17 @@ pub enum MediaFileEngineCategory {
/// A 3D character model.
Character,
/// A 3D non-humanoid model, e.g. dragon, wolf, slime creature, etc.
/// These may or may not have a skeleton that can be animated with IK/FK.
Creature,
/// A 3D skeletal animation sequence that can be applied to a character,
/// creature, etc. Typically, these use Mixamo or Move.ai.
/// A 3D animation.
Animation,
/// A facial blend shape / shape key animation. Currently, these
/// are 100% ARKit.
/// A facial blend shape / shape key animation.
Expression,
/// A large 3D object that can be used as a backdrop for a scene,
/// a.k.a. "set", "stage", etc. These are typically self-contained
/// locations, buildings, or environments.
Location,
/// All items used to add atmosphere to a scene, e.g. furniture, plants,
/// vehicles, radios, and certain props. (We'll reserve the word props for
/// "hand props" later.)
SetDressing,
/// A 3D object that doesn't fit with the other types.
Object,
/// A 3D skybox.
Skybox,
/// An Image Plane (texture that spawns as an object)
ImagePlane,
/// A Video Plane (texture that spawns as an object)
VideoPlane,
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
@@ -67,15 +45,10 @@ impl MediaFileEngineCategory {
match self {
Self::Scene => "scene",
Self::Character => "character",
Self::Creature => "creature",
Self::Animation => "animation",
Self::Expression => "expression",
Self::Location => "location",
Self::SetDressing => "set_dressing",
Self::Object => "object",
Self::Skybox => "skybox",
Self::ImagePlane => "image_plane",
Self::VideoPlane => "video_plane",
}
}
@@ -83,15 +56,10 @@ impl MediaFileEngineCategory {
match value {
"scene" => Ok(Self::Scene),
"character" => Ok(Self::Character),
"creature" => Ok(Self::Creature),
"animation" => Ok(Self::Animation),
"expression" => Ok(Self::Expression),
"location" => Ok(Self::Location),
"set_dressing" => Ok(Self::SetDressing),
"object" => Ok(Self::Object),
"skybox" => Ok(Self::Skybox),
"image_plane" => Ok(Self::ImagePlane),
"video_plane" => Ok(Self::VideoPlane),
_ => Err(format!("invalid value: {:?}", value)),
}
}
@@ -102,15 +70,10 @@ impl MediaFileEngineCategory {
BTreeSet::from([
Self::Scene,
Self::Character,
Self::Creature,
Self::Animation,
Self::Expression,
Self::Location,
Self::SetDressing,
Self::Object,
Self::Skybox,
Self::ImagePlane,
Self::VideoPlane,
])
}
}
@@ -127,63 +90,43 @@ mod tests {
fn test_serialization() {
assert_serialization(MediaFileEngineCategory::Scene, "scene");
assert_serialization(MediaFileEngineCategory::Character, "character");
assert_serialization(MediaFileEngineCategory::Creature, "creature");
assert_serialization(MediaFileEngineCategory::Animation, "animation");
assert_serialization(MediaFileEngineCategory::Expression, "expression");
assert_serialization(MediaFileEngineCategory::Location, "location");
assert_serialization(MediaFileEngineCategory::SetDressing, "set_dressing");
assert_serialization(MediaFileEngineCategory::Object, "object");
assert_serialization(MediaFileEngineCategory::Skybox, "skybox");
assert_serialization(MediaFileEngineCategory::ImagePlane, "image_plane");
assert_serialization(MediaFileEngineCategory::VideoPlane, "video_plane");
}
#[test]
fn test_to_str() {
assert_eq!(MediaFileEngineCategory::Scene.to_str(), "scene");
assert_eq!(MediaFileEngineCategory::Character.to_str(), "character");
assert_eq!(MediaFileEngineCategory::Creature.to_str(), "creature");
assert_eq!(MediaFileEngineCategory::Animation.to_str(), "animation");
assert_eq!(MediaFileEngineCategory::Expression.to_str(), "expression");
assert_eq!(MediaFileEngineCategory::Location.to_str(), "location");
assert_eq!(MediaFileEngineCategory::SetDressing.to_str(), "set_dressing");
assert_eq!(MediaFileEngineCategory::Object.to_str(), "object");
assert_eq!(MediaFileEngineCategory::Skybox.to_str(), "skybox");
assert_eq!(MediaFileEngineCategory::ImagePlane.to_str(), "image_plane");
assert_eq!(MediaFileEngineCategory::VideoPlane.to_str(), "video_plane");
}
#[test]
fn test_from_str() {
assert_eq!(MediaFileEngineCategory::from_str("scene").unwrap(), MediaFileEngineCategory::Scene);
assert_eq!(MediaFileEngineCategory::from_str("character").unwrap(), MediaFileEngineCategory::Character);
assert_eq!(MediaFileEngineCategory::from_str("creature").unwrap(), MediaFileEngineCategory::Creature);
assert_eq!(MediaFileEngineCategory::from_str("animation").unwrap(), MediaFileEngineCategory::Animation);
assert_eq!(MediaFileEngineCategory::from_str("expression").unwrap(), MediaFileEngineCategory::Expression);
assert_eq!(MediaFileEngineCategory::from_str("location").unwrap(), MediaFileEngineCategory::Location);
assert_eq!(MediaFileEngineCategory::from_str("set_dressing").unwrap(), MediaFileEngineCategory::SetDressing);
assert_eq!(MediaFileEngineCategory::from_str("object").unwrap(), MediaFileEngineCategory::Object);
assert_eq!(MediaFileEngineCategory::from_str("skybox").unwrap(), MediaFileEngineCategory::Skybox);
assert_eq!(MediaFileEngineCategory::from_str("image_plane").unwrap(), MediaFileEngineCategory::ImagePlane);
assert_eq!(MediaFileEngineCategory::from_str("video_plane").unwrap(), MediaFileEngineCategory::VideoPlane);
assert!(MediaFileEngineCategory::from_str("foo").is_err());
}
#[test]
fn all_variants() {
let mut variants = MediaFileEngineCategory::all_variants();
assert_eq!(variants.len(), 11);
assert_eq!(variants.len(), 6);
assert_eq!(variants.pop_first(), Some(MediaFileEngineCategory::Scene));
assert_eq!(variants.pop_first(), Some(MediaFileEngineCategory::Character));
assert_eq!(variants.pop_first(), Some(MediaFileEngineCategory::Creature));
assert_eq!(variants.pop_first(), Some(MediaFileEngineCategory::Animation));
assert_eq!(variants.pop_first(), Some(MediaFileEngineCategory::Expression));
assert_eq!(variants.pop_first(), Some(MediaFileEngineCategory::Location));
assert_eq!(variants.pop_first(), Some(MediaFileEngineCategory::SetDressing));
assert_eq!(variants.pop_first(), Some(MediaFileEngineCategory::Object));
assert_eq!(variants.pop_first(), Some(MediaFileEngineCategory::Skybox));
assert_eq!(variants.pop_first(), Some(MediaFileEngineCategory::ImagePlane));
assert_eq!(variants.pop_first(), Some(MediaFileEngineCategory::VideoPlane));
assert_eq!(variants.pop_first(), None);
}
}
@@ -47,9 +47,6 @@ pub enum MediaFileOriginModelType {
#[serde(rename = "stable_diffusion_1_5")]
StableDiffusion15,
#[serde(rename = "gpt_sovits")]
GptSovits,
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
@@ -70,8 +67,7 @@ impl MediaFileOriginModelType {
Self::MocapNet => "mocap_net",
Self::ComfyUi => "comfy_ui",
Self::StyleTTS2 => "styletts2",
Self::StableDiffusion15 => "stable_diffusion_1_5",
Self::GptSovits => "gpt_sovits",
Self::StableDiffusion15 => "stable_diffusion_1_5"
}
}
@@ -87,7 +83,6 @@ impl MediaFileOriginModelType {
"comfy_ui" => Ok(Self::ComfyUi),
"styletts2" => Ok(Self::StyleTTS2),
"stable_diffusion_1_5" => Ok(Self::StableDiffusion15),
"gpt_sovits" => Ok(Self::GptSovits),
_ => Err(format!("invalid value: {:?}", value)),
}
}
@@ -105,8 +100,7 @@ impl MediaFileOriginModelType {
Self::MocapNet,
Self::ComfyUi,
Self::StyleTTS2,
Self::StableDiffusion15,
Self::GptSovits,
Self::StableDiffusion15
])
}
}
@@ -136,7 +130,6 @@ mod tests {
assert_eq!(MediaFileOriginModelType::ComfyUi.to_str(), "comfy_ui");
assert_eq!(MediaFileOriginModelType::StyleTTS2.to_str(), "styletts2");
assert_eq!(MediaFileOriginModelType::StableDiffusion15.to_str(), "stable_diffusion_1_5");
assert_eq!(MediaFileOriginModelType::GptSovits.to_str(), "gpt_sovits");
}
#[test]
@@ -151,14 +144,13 @@ mod tests {
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::StableDiffusion15);
assert_eq!(MediaFileOriginModelType::from_str("gpt_sovits").unwrap(), MediaFileOriginModelType::GptSovits);
assert!(MediaFileOriginModelType::from_str("foo").is_err());
}
#[test]
fn all_variants() {
let mut variants = MediaFileOriginModelType::all_variants();
assert_eq!(variants.len(), 11);
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));
@@ -169,7 +161,6 @@ mod tests {
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::ComfyUi));
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::StyleTTS2));
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::StableDiffusion15));
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::GptSovits));
assert_eq!(variants.pop_first(), None);
}
}
@@ -15,17 +15,14 @@ use utoipa::ToSchema;
pub enum MediaFileType {
// TODO(bt): Deprecate and split into audio mime types; use media_class to represent broadly
/// Audio files: wav, mp3, etc.
#[deprecated]
Audio,
// TODO(bt): Deprecate and split into image mime types; use media_class to represent broadly
/// Image files: png, jpeg, etc.
#[deprecated]
Image,
// TODO(bt): Deprecate and split into video mime types; use media_class to represent broadly
/// Video files: mp4, etc.
#[deprecated]
Video,
/// BVH files (for Bevy)
@@ -64,18 +61,6 @@ pub enum MediaFileType {
/// CSV format. (We use these for ArKit)
Csv,
/// Jpeg images
Jpg,
/// Png images
Png,
/// Gif images
Gif,
/// Mp4 videos
Mp4
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
@@ -100,10 +85,6 @@ impl MediaFileType {
Self::Vmd => "vmd",
Self::Pmx => "pmx",
Self::Csv => "csv",
Self::Jpg => "jpg",
Self::Png => "png",
Self::Gif => "gif",
Self::Mp4 => "mp4",
}
}
@@ -122,10 +103,6 @@ impl MediaFileType {
"vmd" => Ok(Self::Vmd),
"pmx" => Ok(Self::Pmx),
"csv" => Ok(Self::Csv),
"jpg" => Ok(Self::Jpg),
"png" => Ok(Self::Png),
"gif" => Ok(Self::Gif),
"mp4" => Ok(Self::Mp4),
_ => Err(format!("invalid value: {:?}", value)),
}
}
@@ -147,10 +124,6 @@ impl MediaFileType {
Self::Vmd,
Self::Pmx,
Self::Csv,
Self::Jpg,
Self::Png,
Self::Gif,
Self::Mp4,
])
}
}
@@ -178,10 +151,6 @@ mod tests {
assert_serialization(MediaFileType::Vmd, "vmd");
assert_serialization(MediaFileType::Pmx, "pmx");
assert_serialization(MediaFileType::Csv, "csv");
assert_serialization(MediaFileType::Jpg, "jpg");
assert_serialization(MediaFileType::Png, "png");
assert_serialization(MediaFileType::Gif, "gif");
assert_serialization(MediaFileType::Mp4, "mp4");
}
}
@@ -203,10 +172,6 @@ mod tests {
assert_eq!(MediaFileType::Vmd.to_str(), "vmd");
assert_eq!(MediaFileType::Pmx.to_str(), "pmx");
assert_eq!(MediaFileType::Csv.to_str(), "csv");
assert_eq!(MediaFileType::Jpg.to_str(), "jpg");
assert_eq!(MediaFileType::Png.to_str(), "png");
assert_eq!(MediaFileType::Gif.to_str(), "gif");
assert_eq!(MediaFileType::Mp4.to_str(), "mp4");
}
#[test]
@@ -224,10 +189,6 @@ mod tests {
assert_eq!(MediaFileType::from_str("vmd").unwrap(), MediaFileType::Vmd);
assert_eq!(MediaFileType::from_str("pmx").unwrap(), MediaFileType::Pmx);
assert_eq!(MediaFileType::from_str("csv").unwrap(), MediaFileType::Csv);
assert_eq!(MediaFileType::from_str("jpg").unwrap(), MediaFileType::Jpg);
assert_eq!(MediaFileType::from_str("png").unwrap(), MediaFileType::Png);
assert_eq!(MediaFileType::from_str("gif").unwrap(), MediaFileType::Gif);
assert_eq!(MediaFileType::from_str("mp4").unwrap(), MediaFileType::Mp4);
assert!(MediaFileType::from_str("foo").is_err());
}
}
@@ -238,7 +199,7 @@ mod tests {
#[test]
fn all_variants() {
let mut variants = MediaFileType::all_variants();
assert_eq!(variants.len(), 17);
assert_eq!(variants.len(), 13);
assert_eq!(variants.pop_first(), Some(MediaFileType::Audio));
assert_eq!(variants.pop_first(), Some(MediaFileType::Image));
assert_eq!(variants.pop_first(), Some(MediaFileType::Video));
@@ -252,10 +213,6 @@ mod tests {
assert_eq!(variants.pop_first(), Some(MediaFileType::Vmd));
assert_eq!(variants.pop_first(), Some(MediaFileType::Pmx));
assert_eq!(variants.pop_first(), Some(MediaFileType::Csv));
assert_eq!(variants.pop_first(), Some(MediaFileType::Jpg));
assert_eq!(variants.pop_first(), Some(MediaFileType::Png));
assert_eq!(variants.pop_first(), Some(MediaFileType::Gif));
assert_eq!(variants.pop_first(), Some(MediaFileType::Mp4));
assert_eq!(variants.pop_first(), None);
}
}
@@ -9,77 +9,70 @@ use utoipa::ToSchema;
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize, ToSchema)]
pub enum WeightsType {
#[serde(rename = "hifigan_tt2")]
HifiganTacotron2,
#[serde(rename = "rvc_v2")]
RvcV2,
#[serde(rename = "sd_1.5")]
StableDiffusion15,
#[serde(rename = "sdxl")]
StableDiffusionXL,
#[serde(rename = "so_vits_svc")]
SoVitsSvc,
#[serde(rename = "tt2")]
Tacotron2,
#[serde(rename = "loRA")]
LoRA,
#[serde(rename = "vall_e")]
VallE,
#[serde(rename = "comfy_ui")]
ComfyUi,
/// NB: IN production these are filtered to `PublicWeightsType::Tacotron25`.
#[serde(rename = "gpt_so_vits")]
GptSoVits,
#[serde(rename = "hifigan_tt2")]
HifiganTacotron2,
#[serde(rename = "rvc_v2")]
RvcV2,
#[serde(rename = "sd_1.5")]
StableDiffusion15,
#[serde(rename = "sdxl")]
StableDiffusionXL,
#[serde(rename = "so_vits_svc")]
SoVitsSvc,
#[serde(rename = "tt2")]
Tacotron2,
#[serde(rename = "loRA")]
LoRA,
#[serde(rename = "vall_e")]
VallE,
#[serde(rename = "comfy_ui")]
ComfyUi,
}
impl WeightsType {
pub fn to_str(&self) -> &'static str {
match self {
Self::HifiganTacotron2 => "hifigan_tt2",
Self::RvcV2 => "rvc_v2",
Self::StableDiffusion15 => "sd_1.5",
Self::StableDiffusionXL => "sdxl",
Self::SoVitsSvc => "so_vits_svc",
Self::Tacotron2 => "tt2",
Self::LoRA => "loRA",
Self::VallE => "vall_e",
Self::ComfyUi => "comfy_ui",
Self::GptSoVits => "gpt_so_vits",
pub fn to_str(&self) -> &'static str {
match self {
Self::HifiganTacotron2 => "hifigan_tt2",
Self::RvcV2 => "rvc_v2",
Self::StableDiffusion15 => "sd_1.5",
Self::StableDiffusionXL => "sdxl",
Self::SoVitsSvc => "so_vits_svc",
Self::Tacotron2 => "tt2",
Self::LoRA => "loRA",
Self::VallE => "vall_e",
Self::ComfyUi => "comfy_ui",
}
}
}
pub fn from_str(value: &str) -> Result<Self, String> {
match value {
"hifigan_tt2" => Ok(Self::HifiganTacotron2),
"rvc_v2" => Ok(Self::RvcV2),
"sd_1.5" => Ok(Self::StableDiffusion15),
"sdxl" => Ok(Self::StableDiffusionXL),
"so_vits_svc" => Ok(Self::SoVitsSvc),
"tt2" => Ok(Self::Tacotron2),
"loRA" => Ok(Self::LoRA),
"vall_e" => Ok(Self::VallE),
"comfy_ui" => Ok(Self::ComfyUi),
"gpt_so_vits" => Ok(Self::GptSoVits),
_ => Err(format!("invalid value: {:?}", value)),
pub fn from_str(value: &str) -> Result<Self, String> {
match value {
"hifigan_tt2" => Ok(Self::HifiganTacotron2),
"rvc_v2" => Ok(Self::RvcV2),
"sd_1.5" => Ok(Self::StableDiffusion15),
"sdxl" => Ok(Self::StableDiffusionXL),
"so_vits_svc" => Ok(Self::SoVitsSvc),
"tt2" => Ok(Self::Tacotron2),
"loRA" => Ok(Self::LoRA),
"vall_e" => Ok(Self::VallE),
"comfy_ui" => Ok(Self::ComfyUi),
_ => Err(format!("invalid value: {:?}", value)),
}
}
}
pub fn all_variants() -> BTreeSet<Self> {
BTreeSet::from([
Self::HifiganTacotron2,
Self::RvcV2,
Self::StableDiffusion15,
Self::StableDiffusionXL,
Self::SoVitsSvc,
Self::Tacotron2,
Self::LoRA,
Self::VallE,
Self::ComfyUi,
Self::GptSoVits,
])
}
pub fn all_variants() -> BTreeSet<Self> {
BTreeSet::from([
Self::HifiganTacotron2,
Self::RvcV2,
Self::StableDiffusion15,
Self::StableDiffusionXL,
Self::SoVitsSvc,
Self::Tacotron2,
Self::LoRA,
Self::VallE,
Self::ComfyUi,
])
}
}
impl_enum_display_and_debug_using_to_str!(WeightsType);
@@ -91,76 +84,44 @@ mod tests {
use super::*;
#[test]
fn test_to_str() {
assert_eq!(WeightsType::HifiganTacotron2.to_str(), "hifigan_tt2");
assert_eq!(WeightsType::RvcV2.to_str(), "rvc_v2");
assert_eq!(WeightsType::StableDiffusion15.to_str(), "sd_1.5");
assert_eq!(WeightsType::StableDiffusionXL.to_str(), "sdxl");
assert_eq!(WeightsType::SoVitsSvc.to_str(), "so_vits_svc");
assert_eq!(WeightsType::Tacotron2.to_str(), "tt2");
assert_eq!(WeightsType::LoRA.to_str(), "loRA");
assert_eq!(WeightsType::VallE.to_str(), "vall_e");
assert_eq!(WeightsType::ComfyUi.to_str(), "comfy_ui");
assert_eq!(WeightsType::GptSoVits.to_str(), "gpt_so_vits");
}
#[test]
fn test_from_str() {
assert_eq!(WeightsType::from_str("hifigan_tt2").unwrap(), WeightsType::HifiganTacotron2);
assert_eq!(WeightsType::from_str("rvc_v2").unwrap(), WeightsType::RvcV2);
assert_eq!(WeightsType::from_str("sd_1.5").unwrap(), WeightsType::StableDiffusion15);
assert_eq!(WeightsType::from_str("sdxl").unwrap(), WeightsType::StableDiffusionXL);
assert_eq!(WeightsType::from_str("so_vits_svc").unwrap(), WeightsType::SoVitsSvc);
assert_eq!(WeightsType::from_str("tt2").unwrap(), WeightsType::Tacotron2);
assert_eq!(WeightsType::from_str("loRA").unwrap(), WeightsType::LoRA);
assert_eq!(WeightsType::from_str("vall_e").unwrap(), WeightsType::VallE);
assert_eq!(WeightsType::from_str("comfy_ui").unwrap(), WeightsType::ComfyUi);
assert_eq!(WeightsType::from_str("gpt_so_vits").unwrap(), WeightsType::GptSoVits);
assert!(WeightsType::from_str("invalid").is_err());
}
#[test]
fn test_all_variants() {
let variants = WeightsType::all_variants();
assert_eq!(variants.len(), 10);
assert!(variants.contains(&WeightsType::HifiganTacotron2));
assert!(variants.contains(&WeightsType::RvcV2));
assert!(variants.contains(&WeightsType::StableDiffusion15));
assert!(variants.contains(&WeightsType::StableDiffusionXL));
assert!(variants.contains(&WeightsType::SoVitsSvc));
assert!(variants.contains(&WeightsType::Tacotron2));
assert!(variants.contains(&WeightsType::LoRA));
assert!(variants.contains(&WeightsType::VallE));
assert!(variants.contains(&WeightsType::ComfyUi));
assert!(variants.contains(&WeightsType::GptSoVits));
}
mod mechanical_checks {
use super::*;
#[test]
fn variant_length() {
use strum::IntoEnumIterator;
assert_eq!(WeightsType::all_variants().len(), WeightsType::iter().len());
fn test_to_str() {
assert_eq!(WeightsType::HifiganTacotron2.to_str(), "hifigan_tt2");
assert_eq!(WeightsType::RvcV2.to_str(), "rvc_v2");
assert_eq!(WeightsType::StableDiffusion15.to_str(), "sd_1.5");
assert_eq!(WeightsType::StableDiffusionXL.to_str(), "sdxl");
assert_eq!(WeightsType::SoVitsSvc.to_str(), "so_vits_svc");
assert_eq!(WeightsType::Tacotron2.to_str(), "tt2");
assert_eq!(WeightsType::LoRA.to_str(), "loRA");
assert_eq!(WeightsType::VallE.to_str(), "vall_e");
assert_eq!(WeightsType::ComfyUi.to_str(), "comfy_ui");
}
#[test]
fn round_trip() {
for variant in WeightsType::all_variants() {
assert_eq!(variant, WeightsType::from_str(variant.to_str()).unwrap());
assert_eq!(variant, WeightsType::from_str(&format!("{}", variant)).unwrap());
assert_eq!(variant, WeightsType::from_str(&format!("{:?}", variant)).unwrap());
}
fn test_from_str() {
assert_eq!(WeightsType::from_str("hifigan_tt2").unwrap(), WeightsType::HifiganTacotron2);
assert_eq!(WeightsType::from_str("rvc_v2").unwrap(), WeightsType::RvcV2);
assert_eq!(WeightsType::from_str("sd_1.5").unwrap(), WeightsType::StableDiffusion15);
assert_eq!(WeightsType::from_str("sdxl").unwrap(), WeightsType::StableDiffusionXL);
assert_eq!(WeightsType::from_str("so_vits_svc").unwrap(), WeightsType::SoVitsSvc);
assert_eq!(WeightsType::from_str("tt2").unwrap(), WeightsType::Tacotron2);
assert_eq!(WeightsType::from_str("loRA").unwrap(), WeightsType::LoRA);
assert_eq!(WeightsType::from_str("vall_e").unwrap(), WeightsType::VallE);
assert_eq!(WeightsType::from_str("comfy_ui").unwrap(), WeightsType::ComfyUi);
assert!(WeightsType::from_str("invalid").is_err());
}
#[test]
fn serialized_length_ok_for_database() {
const MAX_LENGTH : usize = 16;
for variant in WeightsType::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);
}
fn test_all_variants() {
let variants = WeightsType::all_variants();
assert_eq!(variants.len(), 9);
assert!(variants.contains(&WeightsType::HifiganTacotron2));
assert!(variants.contains(&WeightsType::RvcV2));
assert!(variants.contains(&WeightsType::StableDiffusion15));
assert!(variants.contains(&WeightsType::StableDiffusionXL));
assert!(variants.contains(&WeightsType::SoVitsSvc));
assert!(variants.contains(&WeightsType::Tacotron2));
assert!(variants.contains(&WeightsType::LoRA));
assert!(variants.contains(&WeightsType::VallE));
assert!(variants.contains(&WeightsType::ComfyUi));
}
}
}
@@ -124,10 +124,10 @@ mod tests {
fn serialize() {
let expected = "\"positive\"".to_string(); // NB: Quoted
assert_eq!(expected, serde_json::to_string(&UserRatingValue::Positive).unwrap());
assert_eq!(expected, toml::to_string(&UserRatingValue::Positive).unwrap());
// Just to show this serializes the same as a string
assert_eq!(expected, serde_json::to_string("positive").unwrap());
assert_eq!(expected, toml::to_string("positive").unwrap());
}
#[test]
+2 -2
View File
@@ -112,10 +112,10 @@ mod tests {
fn serialize() {
let expected = "\"author\"".to_string(); // NB: Quoted
assert_eq!(expected, serde_json::to_string(&ViewAs::Author).unwrap());
assert_eq!(expected, toml::to_string(&ViewAs::Author).unwrap());
// Just to show this serializes the same as a string
assert_eq!(expected, serde_json::to_string("author").unwrap());
assert_eq!(expected, toml::to_string("author").unwrap());
}
#[test]
+2 -2
View File
@@ -153,10 +153,10 @@ mod tests {
fn serialize() {
let expected = "\"public\"".to_string(); // NB: Quoted
assert_eq!(expected, serde_json::to_string(&Visibility::Public).unwrap());
assert_eq!(expected, toml::to_string(&Visibility::Public).unwrap());
// Just to show this serializes the same as a string
assert_eq!(expected, serde_json::to_string("public").unwrap());
assert_eq!(expected, toml::to_string("public").unwrap());
}
#[test]
-1
View File
@@ -1,2 +1 @@
pub mod premium_product;
pub mod style_transfer;
@@ -1 +0,0 @@
pub mod premium_product_name;
@@ -1,134 +0,0 @@
use std::collections::BTreeSet;
#[cfg(test)]
use strum::EnumCount;
#[cfg(test)]
use strum::EnumIter;
use utoipa::ToSchema;
/// This enum is not backed by a particular database table.
/// This is used to count premium product uses for free and paid users.
///
/// DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY.
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Deserialize, Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum PremiumProductName {
// NB: These keys are kept short to preserve space
#[serde(rename = "fa")]
FaceAnimator,
// NB: These keys are kept short to preserve space
#[serde(rename = "fm")]
FaceMirror,
// NB: These keys are kept short to preserve space
#[serde(rename = "lip")]
Lipsync,
// NB: These keys are kept short to preserve space
#[serde(rename = "vst")]
VideoStyleTransfer,
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
impl_enum_display_and_debug_using_to_str!(PremiumProductName);
//impl_mysql_enum_coders!(PremiumProductName);
//impl_mysql_from_row!(PremiumProductName);
/// NB: Legacy API for older code.
impl PremiumProductName {
pub fn to_str(&self) -> &'static str {
match self {
Self::FaceAnimator => "fa",
Self::FaceMirror => "fm",
Self::Lipsync => "lip",
Self::VideoStyleTransfer => "vst",
}
}
pub fn from_str(value: &str) -> Result<Self, String> {
match value {
"fa" => Ok(Self::FaceAnimator),
"fm" => Ok(Self::FaceMirror),
"lip" => Ok(Self::Lipsync),
"vst" => Ok(Self::VideoStyleTransfer),
_ => Err(format!("Unknown PremiumProductName: {}", 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::FaceAnimator,
Self::FaceMirror,
Self::Lipsync,
Self::VideoStyleTransfer,
])
}
}
#[cfg(test)]
mod tests {
use crate::no_table::premium_product::premium_product_name::PremiumProductName;
use crate::test_helpers::assert_serialization;
mod serde {
use super::*;
#[test]
fn test_serialization() {
assert_serialization(PremiumProductName::FaceAnimator, "fa");
assert_serialization(PremiumProductName::FaceMirror, "fm");
assert_serialization(PremiumProductName::Lipsync, "lip");
assert_serialization(PremiumProductName::VideoStyleTransfer, "vst");
}
mod impl_methods {
use super::*;
#[test]
fn to_str() {
assert_eq!(PremiumProductName::FaceAnimator.to_str(), "fa");
assert_eq!(PremiumProductName::FaceMirror.to_str(), "fm");
assert_eq!(PremiumProductName::Lipsync.to_str(), "lip");
assert_eq!(PremiumProductName::VideoStyleTransfer.to_str(), "vst");
}
#[test]
fn from_str() {
assert_eq!(PremiumProductName::from_str("fa").unwrap(), PremiumProductName::FaceAnimator);
assert_eq!(PremiumProductName::from_str("fm").unwrap(), PremiumProductName::FaceMirror);
assert_eq!(PremiumProductName::from_str("lip").unwrap(), PremiumProductName::Lipsync);
assert_eq!(PremiumProductName::from_str("vst").unwrap(), PremiumProductName::VideoStyleTransfer);
}
}
mod manual_variant_checks {
use super::*;
#[test]
fn all_variants() {
let variants = PremiumProductName::all_variants();
assert_eq!(variants.len(), 4);
}
}
mod mechanical_checks {
use super::*;
#[test]
fn variant_length() {
use strum::IntoEnumIterator;
assert_eq!(PremiumProductName::all_variants().len(), PremiumProductName::iter().len());
}
#[test]
fn round_trip() {
for variant in PremiumProductName::all_variants() {
assert_eq!(variant, PremiumProductName::from_str(variant.to_str()).unwrap());
assert_eq!(variant, PremiumProductName::from_str(&format!("{}", variant)).unwrap());
assert_eq!(variant, PremiumProductName::from_str(&format!("{:?}", variant)).unwrap());
}
}
}
}
}
@@ -84,10 +84,6 @@ pub enum StyleTransferName {
#[serde(rename = "realistic_cyberpunk")]
RealisticCyberpunk,
// New Styles (2024-06-27)
#[serde(rename = "dreamer")] // TODO: Land this in Gitub
Dreamer,
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
@@ -134,9 +130,6 @@ impl StyleTransferName {
Self::Dragonball => "dragonball",
Self::RealisticMatrix => "realistic_matrix",
Self::RealisticCyberpunk => "realistic_cyberpunk",
// New Styles (2024-06-27)
Self::Dreamer => "dreamer",
}
}
@@ -176,7 +169,6 @@ impl StyleTransferName {
"dragonball" => Ok(Self::Dragonball),
"realistic_matrix" => Ok(Self::RealisticMatrix),
"realistic_cyberpunk" => Ok(Self::RealisticCyberpunk),
"dreamer" => Ok(Self::Dreamer),
_ => Err(format!("Unknown StyleTransferName: {}", value)),
}
}
@@ -217,7 +209,6 @@ impl StyleTransferName {
Self::Dragonball => "31_dragonball.json",
Self::RealisticMatrix => "32_realistic_matrix.json",
Self::RealisticCyberpunk => "33_realistic_cyberpunk.json",
Self::Dreamer => "34_dreamer.json",
}
}
@@ -259,8 +250,6 @@ impl StyleTransferName {
Self::Dragonball,
Self::RealisticMatrix,
Self::RealisticCyberpunk,
Self::Dreamer,
])
}
}
@@ -309,8 +298,6 @@ mod tests {
assert_serialization(StyleTransferName::Dragonball, "dragonball");
assert_serialization(StyleTransferName::RealisticMatrix, "realistic_matrix");
assert_serialization(StyleTransferName::RealisticCyberpunk, "realistic_cyberpunk");
assert_serialization(StyleTransferName::Dreamer, "dreamer");
}
mod impl_methods {
@@ -352,8 +339,6 @@ mod tests {
assert_eq!(StyleTransferName::Dragonball.to_str(), "dragonball");
assert_eq!(StyleTransferName::RealisticMatrix.to_str(), "realistic_matrix");
assert_eq!(StyleTransferName::RealisticCyberpunk.to_str(), "realistic_cyberpunk");
assert_eq!(StyleTransferName::Dreamer.to_str(), "dreamer");
}
#[test]
@@ -393,8 +378,6 @@ mod tests {
assert_eq!(StyleTransferName::from_str("realistic_matrix").unwrap(), StyleTransferName::RealisticMatrix);
assert_eq!(StyleTransferName::from_str("realistic_cyberpunk").unwrap(), StyleTransferName::RealisticCyberpunk);
assert_eq!(StyleTransferName::from_str("dreamer").unwrap(), StyleTransferName::Dreamer);
assert!(StyleTransferName::from_str("foo").is_err());
}
}
@@ -405,7 +388,7 @@ mod tests {
#[test]
fn all_variants() {
let variants = StyleTransferName::all_variants();
assert_eq!(variants.len(), 34);
assert_eq!(variants.len(), 33);
}
}
+4 -8
View File
@@ -3,12 +3,9 @@
use serde::Serialize;
pub fn to_toml<T: Serialize>(t: &T) -> String {
match toml::to_string(t) {
Ok(s) => s.replace("\"", ""), // JSON values are quoted, so we remove quotes
Err(err) => {
panic!("serialization error: {}", err);
}
}
toml::to_string(t)
.expect("serialization error")
.replace("\"", "") // JSON values are quoted, so we remove quotes
}
pub fn to_json<T: Serialize>(t: &T) -> String {
@@ -21,7 +18,6 @@ pub fn to_json<T: Serialize>(t: &T) -> String {
/// This is useful for ensuring stability of serialization.
pub fn assert_serialization<T: Serialize>(t: T, expected: &str) {
// TODO: See if there's a way to check the sqlx serialization
// TODO(bt,2024-06-27): Something broke with serde toml serialization, so we're disabling it
// assert_eq!(&to_toml(&t), expected);
assert_eq!(&to_toml(&t), expected);
assert_eq!(&to_json(&t), expected);
}
-39
View File
@@ -1,39 +0,0 @@
[package]
name = "enums_public"
edition = "2018"
version = "0.0.1"
authors = [
"Brandon Thomas <bt@brand.io>",
"Brandon Thomas <echelon@gmail.com>",
]
publish = false
[lib]
name = "enums_public"
path = "src/lib.rs"
[package.metadata.cargo-udeps.ignore]
normal = ["my-workspace-hack"]
[dependencies]
# Internal
enums = { path = "../enums" }
errors = { path = "../../lib/errors" }
# Workspace hack (faster compile times)
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
# Serialization
serde = "1.0.125"
serde_derive = "1.0.125"
utoipa = { version = "4", features = ["chrono","actix_extras"] }
[dev-dependencies]
# Enum utilities
strum = { version = "0.26", features = ["derive"] }
# Test serialization
serde_json = "1.0.108"
toml = "0.8.14"
@@ -1 +0,0 @@
pub mod public_inference_job_type;
@@ -1,212 +0,0 @@
#[cfg(test)]
use strum::EnumCount;
#[cfg(test)]
use strum::EnumIter;
use utoipa::ToSchema;
use enums::by_table::generic_inference_jobs::inference_job_type::InferenceJobType;
/// Report certain jobs publicly as different from what we actually run.
/// This is so we have an edge against the competition that might try to run
/// the same models or workflows. This won't always make sense, but in some cases it will.
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize, Default, ToSchema, Debug)]
#[serde(rename_all = "snake_case")]
pub enum PublicInferenceJobType {
// Renamed enum variants
/// Instead of `InferenceJobType::LivePortrait` ("live_portrait")
ActingFace,
/// Storyteller Studio and Video Style Transfer Jobs (which we may want to split).
/// These run in Comfy.
/// TODO(bt,2024-07-15): We may segregate these two job types in the future
VideoRender,
GptSovits,
// Everything else is the same
/// Jobs that run ComfyUI workflows
/// This is actually just for Video Style Transfer and Storyteller Studio.
#[deprecated(note = "Use VideoRender instead.")]
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,
/// 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,
/// DEPRECATED. DO NOT USE.
/// Job that converts bevy to workflow files
#[deprecated(note = "This was for Bevy engine's server side rendering.")]
#[serde(rename = "bevy_to_workflow")]
BevyToWorkflow,
#[deprecated(note = "This was for ReRenderAVideo, which we never productionized.")]
/// DEPRECATED. DO NOT USE.
/// Re-render a video is a video style transfer algorithm. We developed code
/// around it, but chose to develop AnimateDiff / ComfyUI support instead.
RerenderAVideo,
}
/// NB: Legacy API for older code.
impl PublicInferenceJobType {
pub fn from_enum(inference_job_type: InferenceJobType) -> Self {
match inference_job_type {
// Renamed variants
InferenceJobType::LivePortrait => Self::ActingFace,
// Conserved variants
InferenceJobType::VideoRender => Self::VideoRender,
InferenceJobType::GptSovits => Self::GptSovits,
InferenceJobType::ComfyUi => Self::ComfyUi,
InferenceJobType::ConvertFbxToGltf => Self::ConvertFbxToGltf,
InferenceJobType::MocapNet => Self::MocapNet,
InferenceJobType::RvcV2 => Self::RvcV2,
InferenceJobType::SadTalker => Self::SadTalker,
InferenceJobType::SoVitsSvc => Self::SoVitsSvc,
InferenceJobType::StableDiffusion => Self::StableDiffusion,
InferenceJobType::StyleTTS2 => Self::StyleTTS2,
InferenceJobType::Tacotron2 => Self::Tacotron2,
InferenceJobType::Unknown => Self::Unknown,
InferenceJobType::BevyToWorkflow => Self::BevyToWorkflow,
InferenceJobType::RerenderAVideo => Self::RerenderAVideo,
}
}
pub fn to_enum(&self) -> InferenceJobType {
match self {
// Renamed variants
Self::ActingFace => InferenceJobType::LivePortrait,
// Conserved variants
Self::VideoRender => InferenceJobType::VideoRender,
Self::GptSovits => InferenceJobType::GptSovits,
Self::ComfyUi => InferenceJobType::ComfyUi,
Self::ConvertFbxToGltf => InferenceJobType::ConvertFbxToGltf,
Self::MocapNet => InferenceJobType::MocapNet,
Self::RvcV2 => InferenceJobType::RvcV2,
Self::SadTalker => InferenceJobType::SadTalker,
Self::SoVitsSvc => InferenceJobType::SoVitsSvc,
Self::StableDiffusion => InferenceJobType::StableDiffusion,
Self::StyleTTS2 => InferenceJobType::StyleTTS2,
Self::Tacotron2 => InferenceJobType::Tacotron2,
Self::Unknown => InferenceJobType::Unknown,
Self::BevyToWorkflow => InferenceJobType::BevyToWorkflow,
Self::RerenderAVideo => InferenceJobType::RerenderAVideo,
}
}
}
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::test_helpers::to_json;
use super::*;
fn override_enums() -> &'static [PublicInferenceJobType; 1] {
&[
PublicInferenceJobType::ActingFace,
]
}
mod override_values {
use super::*;
#[test]
fn acting_face() {
// Public --> Internal
assert_eq!(PublicInferenceJobType::ActingFace.to_enum(), InferenceJobType::LivePortrait);
assert_eq!(to_json(&PublicInferenceJobType::ActingFace.to_enum()), "live_portrait");
// Internal --> Public
assert_eq!(PublicInferenceJobType::from_enum(InferenceJobType::LivePortrait), PublicInferenceJobType::ActingFace);
assert_eq!(to_json(&PublicInferenceJobType::from_enum(InferenceJobType::LivePortrait)), "acting_face");
}
}
mod mechanical_checks {
use super::*;
#[test]
fn public_to_internal() {
let mut tested_count = 0;
for public_variant in PublicInferenceJobType::iter() {
if public_variant == PublicInferenceJobType::ActingFace {
continue; // Can't compare.
}
// Round trip
assert_eq!(public_variant, PublicInferenceJobType::from_enum(public_variant.to_enum()));
let internal_enum_variant = public_variant.to_enum();
let internal_enum_string = to_json(&internal_enum_variant);
let public_enum_string = to_json(&public_variant);
assert_eq!(internal_enum_string, public_enum_string);
tested_count += 1;
}
assert!(tested_count > 1);
assert_eq!(tested_count, PublicInferenceJobType::iter().len() - override_enums().len());
}
#[test]
fn internal_to_public() {
let mut tested_count = 0;
for internal_variant in InferenceJobType::all_variants() {
if internal_variant == InferenceJobType::LivePortrait {
continue; // Can't compare.
}
// Round trip
assert_eq!(internal_variant, PublicInferenceJobType::from_enum(internal_variant).to_enum());
let public_enum_variant = PublicInferenceJobType::from_enum(internal_variant);
let public_enum_string = to_json(&public_enum_variant);
let internal_enum_string = to_json(&internal_variant);
// Same serialization
assert_eq!(internal_enum_string, public_enum_string);
tested_count += 1;
}
assert!(tested_count > 1);
assert_eq!(tested_count, InferenceJobType::all_variants().len() - override_enums().len());
}
}
}
@@ -1,2 +0,0 @@
pub mod generic_inference_jobs;
pub mod model_weights;
@@ -1 +0,0 @@
pub mod public_weights_types;
@@ -1,162 +0,0 @@
#[cfg(test)]
use strum::EnumCount;
#[cfg(test)]
use strum::EnumIter;
use utoipa::ToSchema;
use enums::by_table::model_weights::weights_types::WeightsType;
/// Report certain models publicly as different from what we actually use.
/// This is so we have an edge against the competition that might try to run
/// the same models. This won't always make sense, but in some cases it will.
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize, ToSchema, Debug)]
pub enum PublicWeightsType {
// Renamed enum variants
/// Instead of `WeightsType::GptSoVits` ("gpt_so_vits")
#[serde(rename = "tacotron2.5")]
Tacotron2_5,
// Everything else is the same
#[serde(rename = "hifigan_tt2")]
HifiganTacotron2,
#[serde(rename = "rvc_v2")]
RvcV2,
#[serde(rename = "sd_1.5")]
StableDiffusion15,
#[serde(rename = "sdxl")]
StableDiffusionXL,
#[serde(rename = "so_vits_svc")]
SoVitsSvc,
#[serde(rename = "tt2")]
Tacotron2,
#[serde(rename = "loRA")]
LoRA,
#[serde(rename = "vall_e")]
VallE,
#[serde(rename = "comfy_ui")]
ComfyUi,
}
impl PublicWeightsType {
pub fn from_enum(weights_type: WeightsType) -> Self {
match weights_type {
// Renamed variants
WeightsType::GptSoVits => Self::Tacotron2_5,
// Conserved variants
WeightsType::HifiganTacotron2 => Self::HifiganTacotron2,
WeightsType::RvcV2 => Self::RvcV2,
WeightsType::StableDiffusion15 => Self::StableDiffusion15,
WeightsType::StableDiffusionXL => Self::StableDiffusionXL,
WeightsType::SoVitsSvc => Self::SoVitsSvc,
WeightsType::Tacotron2 => Self::Tacotron2,
WeightsType::LoRA => Self::LoRA,
WeightsType::VallE => Self::VallE,
WeightsType::ComfyUi => Self::ComfyUi,
}
}
pub fn to_enum(&self) -> WeightsType {
match self {
// Renamed variants
Self::Tacotron2_5 => WeightsType::GptSoVits,
// Conserved variants
Self::HifiganTacotron2 => WeightsType::HifiganTacotron2,
Self::RvcV2 => WeightsType::RvcV2,
Self::StableDiffusion15 => WeightsType::StableDiffusion15,
Self::StableDiffusionXL => WeightsType::StableDiffusionXL,
Self::SoVitsSvc => WeightsType::SoVitsSvc,
Self::Tacotron2 => WeightsType::Tacotron2,
Self::LoRA => WeightsType::LoRA,
Self::VallE => WeightsType::VallE,
Self::ComfyUi => WeightsType::ComfyUi,
}
}
}
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::test_helpers::to_json;
use super::*;
fn override_enums() -> &'static [PublicWeightsType; 1] {
&[
PublicWeightsType::Tacotron2_5,
]
}
mod override_values {
use super::*;
#[test]
fn gpt_so_vits() {
// Public --> Internal
assert_eq!(PublicWeightsType::Tacotron2_5.to_enum(), WeightsType::GptSoVits);
assert_eq!(to_json(&PublicWeightsType::Tacotron2_5.to_enum()), "gpt_so_vits");
// Internal --> Public
assert_eq!(PublicWeightsType::from_enum(WeightsType::GptSoVits), PublicWeightsType::Tacotron2_5);
assert_eq!(to_json(&PublicWeightsType::from_enum(WeightsType::GptSoVits)), "tacotron2.5");
}
}
mod mechanical_checks {
use super::*;
#[test]
fn public_to_internal() {
let mut tested_count = 0;
for public_variant in PublicWeightsType::iter() {
if public_variant == PublicWeightsType::Tacotron2_5 {
continue; // Can't compare.
}
// Round trip
assert_eq!(public_variant, PublicWeightsType::from_enum(public_variant.to_enum()));
let internal_enum_variant = public_variant.to_enum();
let internal_enum_string = to_json(&internal_enum_variant);
let public_enum_string = to_json(&public_variant);
assert_eq!(internal_enum_string, public_enum_string);
tested_count += 1;
}
assert!(tested_count > 1);
assert_eq!(tested_count, PublicWeightsType::iter().len() - override_enums().len());
}
#[test]
fn internal_to_public() {
let mut tested_count = 0;
for internal_variant in WeightsType::all_variants() {
if internal_variant == WeightsType::GptSoVits {
continue; // Can't compare.
}
// Round trip
assert_eq!(internal_variant, PublicWeightsType::from_enum(internal_variant).to_enum());
let public_enum_variant = PublicWeightsType::from_enum(internal_variant);
let public_enum_string = to_json(&public_enum_variant);
let internal_enum_string = to_json(&internal_variant);
// Same serialization
assert_eq!(internal_enum_string, public_enum_string);
tested_count += 1;
}
assert!(tested_count > 1);
assert_eq!(tested_count, WeightsType::all_variants().len() - override_enums().len());
}
}
}
-26
View File
@@ -1,26 +0,0 @@
//! enums_public
//!
//! Sometimes we want to hide which enums we allow over the API
//! to keep competitors in the dark about what we're doing.
//!
// 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)]
#[macro_use] extern crate serde_derive;
#[cfg(test)] pub mod test_helpers;
pub mod by_table;
@@ -1,27 +0,0 @@
//! Only imported in tests
use serde::Serialize;
pub fn to_toml<T: Serialize>(t: &T) -> String {
match toml::to_string(t) {
Ok(s) => s.replace("\"", ""), // JSON values are quoted, so we remove quotes
Err(err) => {
panic!("serialization error: {}", err);
}
}
}
pub fn to_json<T: Serialize>(t: &T) -> String {
serde_json::to_string(t)
.expect("serialization error")
.replace("\"", "") // JSON values are quoted, so we remove quotes
}
/// Assert that the Serialize is represented by the expected string value.
/// This is useful for ensuring stability of serialization.
pub fn assert_serialization<T: Serialize>(t: T, expected: &str) {
// TODO: See if there's a way to check the sqlx serialization
// TODO(bt,2024-06-27): Something broke with serde toml serialization, so we're disabling it
// assert_eq!(&to_toml(&t), expected);
assert_eq!(&to_json(&t), expected);
}
+1 -1
View File
@@ -36,4 +36,4 @@ sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls",
[dev-dependencies]
# Test serialization
#serde_json = "1.0.64"
toml = "0.8.14"
toml = "0.5.8"
+2 -2
View File
@@ -41,8 +41,8 @@ utoipa = { version = "4", features = ["chrono","actix_extras"] }
[dev-dependencies]
# Enum utilities
strum = { version = "0.26", features = ["derive"] }
strum = { version = "0.25", features = ["derive"] }
# Test serialization
serde_json = "1.0.108"
toml = "0.8.14"
toml = "0.5.8"
@@ -12,5 +12,4 @@ use crate::prefixes::TokenPrefix;
pub struct MediaFileToken(pub String);
impl_string_token!(MediaFileToken);
impl_mysql_token_from_row!(MediaFileToken);
impl_crockford_generator!(MediaFileToken, 32usize, TokenPrefix::MediaFile, CrockfordLower);
@@ -40,7 +40,7 @@ clap = "4.4.8"
rand = "0.8.5"
# Runtime
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
tokio = { version = "1.38.0", features = ["macros"] }
tokio = { version = "1.34.0", features = ["macros"] }
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
@@ -16,7 +16,6 @@ pub fn get_bucket_clients() -> AnyhowResult<BucketClients> {
let region_name = easyenv::get_env_string_required("REGION_NAME")?;
let public_bucket_name = easyenv::get_env_string_required("PUBLIC_BUCKET_NAME")?;
let private_bucket_name = easyenv::get_env_string_required("PRIVATE_BUCKET_NAME")?;
let s3_compatible_endpoint_url = easyenv::get_env_string_or_default("S3_COMPATIBLE_ENDPOINT_URL", "https://storage.googleapis.com");
// NB: Long timeout for dev rust builds to upload to cloud buckets.
// Unoptimized binaries sometimes take a lot of time to upload, presumably due to unoptimized code.
@@ -30,7 +29,6 @@ pub fn get_bucket_clients() -> AnyhowResult<BucketClients> {
&secret_key,
&region_name,
&public_bucket_name,
&s3_compatible_endpoint_url,
None,
Some(bucket_timeout),
)?;
@@ -42,7 +40,6 @@ pub fn get_bucket_clients() -> AnyhowResult<BucketClients> {
&secret_key,
&region_name,
&private_bucket_name,
&s3_compatible_endpoint_url,
None,
Some(bucket_timeout),
)?;
@@ -37,7 +37,7 @@ log = "0.4.14"
# Runtime
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
tokio = { version = "1.38.0", features = ["macros"] }
tokio = { version = "1.34.0", features = ["macros"] }
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
@@ -43,8 +43,6 @@ pub async fn main() -> AnyhowResult<()> {
let region_name = easyenv::get_env_string_required("REGION_NAME")?;
let public_bucket_name = easyenv::get_env_string_required("PUBLIC_BUCKET_NAME")?;
let s3_compatible_endpoint_url = easyenv::get_env_string_or_default(
"S3_COMPATIBLE_ENDPOINT_URL", "");
let bucket_timeout = easyenv::get_env_duration_seconds_or_default(
"BUCKET_TIMEOUT_SECONDS", Duration::from_secs(60 * 5));
@@ -55,7 +53,6 @@ pub async fn main() -> AnyhowResult<()> {
&secret_key,
&region_name,
&public_bucket_name,
&s3_compatible_endpoint_url,
None,
Some(bucket_timeout),
)?;
+1 -1
View File
@@ -26,4 +26,4 @@ my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
# External
clap = "4.4.8"
tokio = { version = "1.38.0", features = ["macros"] }
tokio = { version = "1.34.0", features = ["macros"] }
+2 -1
View File
@@ -16,9 +16,10 @@ path = "src/main.rs"
normal = ["my-workspace-hack"]
[dependencies]
# Internal
container_common = { path = "../../../lib/deprecated/container_common" }
easyenv = { path = "../../../lib/easyenv" }
errors = { path = "../../../lib/errors" }
# Workspace hack (faster compile times)
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
+1 -1
View File
@@ -1,7 +1,7 @@
use log::info;
use container_common::anyhow_result::AnyhowResult;
use easyenv::init_all_with_default_logging;
use errors::AnyhowResult;
fn main() -> AnyhowResult<()> {
init_all_with_default_logging(None);
+3 -2
View File
@@ -18,6 +18,7 @@ normal = ["my-workspace-hack"]
[dependencies]
# Internal
buckets = { path = "../../../_types/buckets" }
container_common = { path = "../../../lib/deprecated/container_common" }
easyenv = { path = "../../../lib/easyenv" }
enums = { path = "../../../_types/enums" }
errors = { path = "../../../lib/errors" }
@@ -31,11 +32,11 @@ my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
# External
clap = "4.4.8"
dotenv = "0.15.0"
itertools = "0.13.0"
itertools = "0.11.0"
log = "0.4.14"
# Bigger pieces
tokio = { version = "1.38.0", features = ["macros"] }
tokio = { version = "1.34.0", features = ["macros"] }
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
# Serialization
+1 -1
View File
@@ -2,8 +2,8 @@ use log::info;
use sqlx::{MySql, Pool};
use sqlx::mysql::MySqlPoolOptions;
use container_common::anyhow_result::AnyhowResult;
use easyenv::init_all_with_default_logging;
use errors::AnyhowResult;
use crate::cli_args::Action;
use crate::operations::delete_all_anonymous_user_images::delete_all_anonymous_user_images::delete_all_anonymous_user_images;
@@ -8,7 +8,7 @@ use sqlx::{MySql, Pool};
use enums::by_table::media_files::media_file_type::MediaFileType;
use enums::common::view_as::ViewAs;
use errors::AnyhowResult;
use mysql_queries::queries::media_files::delete::delete_media_file::delete_media_file_as_mod;
use mysql_queries::queries::media_files::delete_media_file::delete_media_file_as_mod;
use mysql_queries::queries::media_files::list::list_media_files::{list_media_files, ListMediaFilesArgs};
use crate::cli_args::Args;
@@ -6,7 +6,7 @@ use sqlx::{MySql, Pool};
use enums::common::view_as::ViewAs;
use errors::{anyhow, AnyhowResult};
use mysql_queries::queries::media_files::delete::delete_media_file::delete_media_file_as_mod;
use mysql_queries::queries::media_files::delete_media_file::delete_media_file_as_mod;
use mysql_queries::queries::media_files::list::list_media_files_for_user::{list_media_files_for_user, ListMediaFileForUserArgs};
use mysql_queries::queries::users::user_profiles::get_user_profile_by_username::get_user_profile_by_username;
@@ -33,10 +33,10 @@ my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
# External
clap = "4.4.8"
log = "0.4.14"
strum = { version = "0.26", features = ["derive"] }
strum = { version = "0.25", features = ["derive"] }
# Bigger pieces
tokio = { version = "1.38.0", features = ["macros"] }
tokio = { version = "1.34.0", features = ["macros"] }
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
elasticsearch = "8.5.0-alpha.1"
@@ -23,17 +23,10 @@ pub enum Environment {
pub enum Action {
ReindexTts,
SearchTts,
/// Reindex model_weights into ElasticSearch
ReindexModelWeights,
/// Search the model weights
SearchModelWeights,
/// Reindex media_files into ElasticSearch
/// For now, this is just media files that are relevant to the engine.
ReindexMediaFiles,
SearchMediaFiles,
}
#[derive(Parser, Debug)]
@@ -20,8 +20,6 @@ use errors::AnyhowResult;
use crate::cli_args::{Action, Environment, parse_cli_args};
use crate::plans::create_all_model_weight_documents::create_all_model_weight_documents;
use crate::plans::create_all_tts_documents::create_all_tts_documents;
use crate::plans::media_files::create_dimensional_media_file_documents::create_dimensional_media_file_documents;
use crate::plans::media_files::test_search_media_files_documents::test_search_media_files;
pub mod cli_args;
pub mod plans;
@@ -64,14 +62,6 @@ pub async fn main() -> AnyhowResult<()> {
println!("Result: {:#?}", result);
}
}
Action::ReindexMediaFiles => {
info!("Reindexing media files...");
create_dimensional_media_file_documents(&mysql, &elasticsearch).await?;
}
Action::SearchMediaFiles => {
info!("Search media files...");
test_search_media_files(&elasticsearch).await?;
}
}
info!("Done!");
@@ -13,11 +13,7 @@ use errors::AnyhowResult;
use mysql_queries::queries::model_weights::list::list_model_weights_for_elastic_search_backfill_using_cursor::{list_model_weights_for_elastic_search_backfill_using_cursor, ModelWeightForElasticsearchRecord};
use storyteller_root::get_storyteller_rust_root;
pub async fn create_all_model_weight_documents(
mysql: &Pool<MySql>,
elasticsearch: &Elasticsearch
) -> AnyhowResult<()> {
pub async fn create_all_model_weight_documents(mysql: &Pool<MySql>, elasticsearch: &Elasticsearch) -> AnyhowResult<()> {
info!("Create all model weight documents.");
// TODO(bt, 2024-01-10): expose this as a CLI flag
@@ -67,8 +63,7 @@ async fn create_document_from_record(elasticsearch: &Elasticsearch, record: Mode
weights_type: record.weights_type,
weights_category: record.weights_category,
title: record.title.clone(),
title_as_keyword: record.title,
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,
@@ -1,190 +0,0 @@
use std::collections::HashSet;
use std::fs::read_to_string;
use std::iter::FromIterator;
use elasticsearch::{BulkOperation, BulkParts, Elasticsearch};
use log::{error, info, warn};
use serde_json::Value;
use sqlx::{MySql, Pool};
use elasticsearch_schema::documents::media_file_document::{MEDIA_FILE_INDEX, MediaFileDocument};
use elasticsearch_schema::traits::document::Document;
use elasticsearch_schema::utils::create_index_if_not_exists::{create_index_if_not_exists, CreateIndexArgs};
use enums::by_table::media_files::media_file_class::MediaFileClass;
use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory;
use enums::by_table::media_files::media_file_type::MediaFileType;
use errors::AnyhowResult;
use mysql_queries::queries::media_files::list::list_media_files_for_elastic_search_backfill_using_cursor::{list_media_files_for_elastic_search_backfill_using_cursor, ListArgs, MediaFileForElasticsearchRecord};
use mysql_queries::queries::model_weights::list::list_model_weights_for_elastic_search_backfill_using_cursor::{list_model_weights_for_elastic_search_backfill_using_cursor, ModelWeightForElasticsearchRecord};
use storyteller_root::get_storyteller_rust_root;
pub async fn create_dimensional_media_file_documents(
mysql: &Pool<MySql>,
elasticsearch: &Elasticsearch
) -> AnyhowResult<()> {
info!("Create dimensional media file documents.");
// TODO(bt, 2024-01-10): expose this as a CLI flag
const DELETE_EXISTING_INDEX : bool = true;
const PAGE_SIZE : usize = 1000;
create_media_file_index(&elasticsearch, DELETE_EXISTING_INDEX).await?;
let mut cursor = 0;
loop {
info!("Cursor: {cursor}");
let results = list_media_files_for_elastic_search_backfill_using_cursor(ListArgs {
mysql_pool: mysql,
page_size: PAGE_SIZE,
cursor,
maybe_filter_engine_categories: Some(&HashSet::from_iter(vec![
MediaFileEngineCategory::Animation,
MediaFileEngineCategory::Character,
MediaFileEngineCategory::Creature,
MediaFileEngineCategory::Expression,
MediaFileEngineCategory::ImagePlane,
MediaFileEngineCategory::Location,
MediaFileEngineCategory::Object,
MediaFileEngineCategory::Scene,
MediaFileEngineCategory::SetDressing,
MediaFileEngineCategory::Skybox,
MediaFileEngineCategory::VideoPlane,
])),
maybe_filter_media_types: Some(&HashSet::from_iter(vec![
// Engine types
MediaFileType::Csv,
MediaFileType::Fbx,
MediaFileType::Glb,
MediaFileType::SceneJson,
// Image types
MediaFileType::Gif,
MediaFileType::Jpg,
MediaFileType::Png,
])),
maybe_filter_media_classes: Some(&HashSet::from_iter(vec![
MediaFileClass::Image,
MediaFileClass::Dimensional,
])),
}).await?;
info!("Results length: {}", results.len());
if results.is_empty() {
info!("No more results at cursor {cursor}");
break;
}
let maybe_last_id = results.last().map(|result| result.id);
match maybe_last_id {
Some(last_id) => cursor = last_id as usize,
None => {
warn!("No final ID at cursor {cursor}");
break;
}
}
for result in results {
create_document_from_record(elasticsearch, result).await?;
}
}
Ok(())
}
async fn create_document_from_record(elasticsearch: &Elasticsearch, record: MediaFileForElasticsearchRecord) -> AnyhowResult<()> {
info!("Create record for {:?}", record.token);
let is_deleted = record.user_deleted_at.is_some() || record.mod_deleted_at.is_some();
let document = MediaFileDocument {
token: record.token,
media_class: record.media_class,
media_type: record.media_type,
maybe_media_subtype: record.maybe_media_subtype,
maybe_engine_category: record.maybe_engine_category,
maybe_animation_type: record.maybe_animation_type,
maybe_mime_type: record.maybe_mime_type,
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,
creator_set_visibility: record.creator_set_visibility,
maybe_title: record.maybe_title.clone(),
maybe_title_as_keyword: record.maybe_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,
maybe_creator_user_token: record.maybe_creator_user_token,
maybe_creator_username: record.maybe_creator_username,
maybe_creator_display_name: record.maybe_creator_display_name,
maybe_creator_gravatar_hash: record.maybe_creator_gravatar_hash,
is_featured: record.is_featured,
created_at: record.created_at,
updated_at: record.updated_at,
user_deleted_at: record.user_deleted_at,
mod_deleted_at: record.mod_deleted_at,
is_deleted,
};
let op : BulkOperation<_> = BulkOperation::index(&document)
.id(document.get_document_id())
.into();
let response = elasticsearch
.bulk(BulkParts::Index(MEDIA_FILE_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();
// TODO: retry failures
error!("Errors during indexing. Failures: {}", failed.len());
}
Ok(())
}
// NB: Adapted from elasticsearch crate examples source
async fn create_media_file_index(client: &Elasticsearch, delete_existing: bool) -> AnyhowResult<()> {
info!("Creating model media file index...");
let index_path = get_storyteller_rust_root()
.join("_elasticsearch/index_definitions/media_files_v1.json");
info!("Reading index file: {:?}", index_path);
let index_definition = read_to_string(index_path)?;
create_index_if_not_exists(CreateIndexArgs {
client,
index_name: MEDIA_FILE_INDEX,
index_definition: &index_definition,
delete_existing,
}).await?;
Ok(())
}
@@ -1,2 +0,0 @@
pub mod create_dimensional_media_file_documents;
pub mod test_search_media_files_documents;
@@ -1,30 +0,0 @@
use std::collections::HashSet;
use std::iter::FromIterator;
use elasticsearch::Elasticsearch;
use elasticsearch_schema::searches::search_media_files::{search_media_files, SearchArgs};
use enums::by_table::media_files::media_file_class::MediaFileClass;
use errors::AnyhowResult;
pub async fn test_search_media_files(client: &Elasticsearch) -> AnyhowResult<()> {
let results = search_media_files(SearchArgs {
search_term: "cac",
is_featured: None,
maybe_creator_user_token: None,
maybe_media_classes: Some(HashSet::from_iter(vec![
MediaFileClass::Dimensional,
MediaFileClass::Image,
])),
maybe_media_types: None,
maybe_engine_categories: None,
client,
}).await?;
for result in results {
println!("Result: {:#?}", result);
}
Ok(())
}
@@ -1,3 +1,2 @@
pub mod create_all_model_weight_documents;
pub mod create_all_tts_documents;
pub mod media_files;
pub mod create_all_tts_documents;
@@ -35,11 +35,11 @@ my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
# External
clap = "4.4.8"
log = "0.4.14"
strum = { version = "0.26", features = ["derive"] }
strum = { version = "0.25", features = ["derive"] }
tempdir = "0.3.7"
# Async + clients
tokio = { version = "1.38.0", features = ["macros"] }
tokio = { version = "1.34.0", features = ["macros"] }
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
# Serialization
@@ -75,8 +75,6 @@ pub fn get_bucket_client(env_var_suffix: &str) -> AnyhowResult<BucketClient> {
let region_name = easyenv::get_env_string_required(&format!("REGION_NAME_{env_var_suffix}"))?;
let bucket_name = easyenv::get_env_string_required(&format!("BUCKET_NAME_{env_var_suffix}"))?;
let s3_compatible_endpoint_url = easyenv::get_env_string_or_default(
&format!("S3_COMPATIBLE_ENDPOINT_URL_{env_var_suffix}"), "https://storage.googleapis.com");
// NB: Long timeout for dev rust builds to upload to cloud buckets.
// Unoptimized binaries sometimes take a lot of time to upload, presumably due to unoptimized code.
let bucket_timeout = easyenv::get_env_duration_seconds_or_default(
@@ -89,7 +87,6 @@ pub fn get_bucket_client(env_var_suffix: &str) -> AnyhowResult<BucketClient> {
&secret_key,
&region_name,
&bucket_name,
&s3_compatible_endpoint_url,
None,
Some(bucket_timeout),
)?)
+3 -3
View File
@@ -29,10 +29,10 @@ log = "0.4.14"
# Actix
actix-cors = "0.6.4"
actix-http = "3.7.0"
actix-web = "4.7.0"
actix-http = "3.4.0"
actix-web = "4.4.0"
url = { version = "2.5.0", features = [] }
[dev-dependencies]
actix-rt = "2.10.0"
actix-rt = "2.9.0"
speculoos = "0.11.0" # Fluent assertions
@@ -2,17 +2,14 @@ use actix_cors::Cors;
use crate::util::netlify_branch_domain_matches::netlify_branch_domain_matches;
pub fn add_storyteller_studio(cors: Cors, _is_production: bool) -> Cors {
cors.allowed_origin("https://studio.storyteller.ai")
.allowed_origin("https://studio-staging.studio.storyteller.ai")
.allowed_origin("https://studio-testing.studio.storyteller.ai")
.allowed_origin_fn(|origin, _req_head| {
netlify_branch_domain_matches(origin, "storytellerstudio.netlify.app")
})
pub fn add_gotta_go_fast_test_branches(cors: Cors, _is_production: bool) -> Cors {
cors.allowed_origin("http://localhost:5173")
.allowed_origin_fn(|origin, _req_head| {
netlify_branch_domain_matches(origin, "pipeline-gottagofast.netlify.app")
})
.allowed_origin("http://localhost:5173")
.allowed_origin_fn(|origin, _req_head| {
netlify_branch_domain_matches(origin, "storytellerstudio.netlify.app")
})
}
#[cfg(test)]
@@ -56,40 +53,18 @@ mod tests {
#[actix_rt::test]
async fn studio_main() {
let production_cors = build_cors_config(ServerEnvironment::Production);
assert_origin_ok(&production_cors, "https://studio.storyteller.ai").await;
assert_origin_invalid(&production_cors, "https://studiofake.storyteller.ai").await;
}
#[actix_rt::test]
async fn studio_staging() {
let production_cors = build_cors_config(ServerEnvironment::Production);
assert_origin_ok(&production_cors, "https://studio-staging.studio.storyteller.ai").await;
assert_origin_invalid(&production_cors, "https://studio-staging-fake.studio.storyteller.ai").await;
}
#[actix_rt::test]
async fn studio_testing() {
let production_cors = build_cors_config(ServerEnvironment::Production);
assert_origin_ok(&production_cors, "https://studio-testing.studio.storyteller.ai").await;
assert_origin_invalid(&production_cors, "https://studio-testing-fake.studio.storyteller.ai").await;
}
#[actix_rt::test]
async fn studio_netlify() {
let production_cors = build_cors_config(ServerEnvironment::Production);
assert_origin_ok(&production_cors, "https://storytellerstudio.netlify.app").await;
assert_origin_invalid(&production_cors, "https://storytellerstudiofake.netlify.app").await;
}
#[actix_rt::test]
async fn studio_netlify_branch_deploy() {
async fn studio_branch_deploy() {
let production_cors = build_cors_config(ServerEnvironment::Production);
assert_origin_ok(&production_cors, "https://foo--storytellerstudio.netlify.app").await;
}
#[actix_rt::test]
async fn studio_netlify_deploy_preview() {
async fn studio_deploy_preview() {
let production_cors = build_cors_config(ServerEnvironment::Production);
assert_origin_ok(&production_cors, "https://deploy-preview-123--storytellerstudio.netlify.app").await;
}
@@ -1,5 +1,5 @@
pub (crate) mod development_only;
pub (crate) mod fakeyou;
pub (crate) mod storyteller_studio;
pub (crate) mod gottagofast;
pub (crate) mod legacy;
pub (crate) mod storyteller;
+2 -2
View File
@@ -5,7 +5,7 @@ use reusable_types::server_environment::ServerEnvironment;
use crate::configs::development_only::add_development_only;
use crate::configs::fakeyou::{add_fakeyou, add_fakeyou_dev_proxy};
use crate::configs::storyteller_studio::add_storyteller_studio;
use crate::configs::gottagofast::add_gotta_go_fast_test_branches;
use crate::configs::legacy::{add_legacy_storyteller_stream, add_legacy_trumped, add_legacy_vocodes, add_power_stream};
use crate::configs::storyteller::{add_storyteller, add_storyteller_dev_proxy};
@@ -34,7 +34,7 @@ fn do_build_cors_config(is_production: bool) -> Cors {
cors = add_fakeyou_dev_proxy(cors, is_production);
cors = add_storyteller(cors, is_production);
cors = add_storyteller_dev_proxy(cors, is_production);
cors = add_storyteller_studio(cors, is_production);
cors = add_gotta_go_fast_test_branches(cors, is_production);
// Legacy
cors = add_legacy_trumped(cors, is_production);
+3 -3
View File
@@ -24,11 +24,11 @@ errors = { path = "../../lib/errors" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
# Actix
actix-http = "3.7.0"
actix-rt = "2.10.0"
actix-http = "3.4.0"
actix-rt = "2.9.0"
actix-service = "2.0.2"
actix-utils = "3.0.1"
actix-web = "4.7.0"
actix-web = "4.4.0"
# IPv4 and v6 CIDRs (crate has few dependencies)
cidr-utils = "0.5.11"
@@ -32,13 +32,13 @@ my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
# External
anyhow = "1.0.86"
log = "0.4.14"
env_logger = "0.11.3"
env_logger = "0.10.1"
async-trait = "0.1.75"
# The "never-encode-slash" feature seems to have been removed:
# https://github.com/durch/rust-s3/pull/254
rust-s3 = { version = "0.34.0" }
rust-s3 = { version = "0.33.0" }
# Runtime
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
tokio = { version = "1.38.0", features = ["macros"] }
tokio = { version = "1.34.0", features = ["macros"] }
@@ -47,7 +47,6 @@ impl BucketClient {
secret_key: &str,
region_name: &str,
bucket_name: &str,
s3_endpoint: &str,
optional_bucket_root: Option<&str>,
// See underlying docs for timeout details.
bucket_request_timeout: Option<Duration>,
@@ -64,14 +63,14 @@ impl BucketClient {
// NB: The GCS buckets aren't supported by default.
let region = Region::Custom {
region: region_name.to_owned(),
endpoint: s3_endpoint.to_owned(),
endpoint: "https://storage.googleapis.com".to_owned(),
};
let mut bucket = Bucket::new(&bucket_name, region, credentials)?;
bucket.set_request_timeout(bucket_request_timeout);
bucket.set_path_style();
// bucket.set_subdomain_style();
bucket.set_subdomain_style();
let optional_bucket_root = optional_bucket_root.map(|s| s.to_string());
@@ -28,7 +28,6 @@ pub struct BucketOrchestration {
region_name: String,
public_bucket_name: String,
private_bucket_name: String,
s3_compatible_endpoint_url: String,
}
#[async_trait]
@@ -64,17 +63,12 @@ impl BucketOrchestration {
let region_name = easyenv::get_env_string_required("REGION_NAME")?;
let public_bucket_name = easyenv::get_env_string_required("PUBLIC_BUCKET_NAME")?;
let private_bucket_name = easyenv::get_env_string_required("PRIVATE_BUCKET_NAME")?;
let s3_compatible_endpoint_url = easyenv::get_env_string_or_default(
"S3_COMPATIBLE_ENDPOINT_URL",
"https://storage.googleapis.com",
);
let bucket_orchestration = BucketOrchestration::new(
access_key,
secret_key,
region_name,
s3_compatible_endpoint_url,
public_bucket_name,
private_bucket_name,
);
@@ -86,7 +80,6 @@ impl BucketOrchestration {
access_key: String,
secret_key: String,
region_name: String,
s3_compatible_endpoint_url: String,
public_bucket_name: String,
private_bucket_name: String) -> Self {
Self {
@@ -95,7 +88,6 @@ impl BucketOrchestration {
region_name,
public_bucket_name,
private_bucket_name,
s3_compatible_endpoint_url,
}
}
@@ -110,7 +102,6 @@ impl BucketOrchestration {
&self.access_key,
&self.secret_key,
&self.region_name,
&self.s3_compatible_endpoint_url,
&self.public_bucket_name,
None,
Some(bucket_timeout),
@@ -121,7 +112,6 @@ impl BucketOrchestration {
&self.access_key,
&self.secret_key,
&self.region_name,
&self.s3_compatible_endpoint_url,
&self.private_bucket_name,
None,
Some(bucket_timeout),
@@ -15,5 +15,5 @@ my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
# External
easyenv = { path = "../../easyenv" }
log = "0.4.20"
reqwest = "0.12.4"
reqwest = "0.11.24"
serde_json = "1.0.113"
@@ -1,62 +1,6 @@
use log::error;
use serde_json::{json, Value};
#[derive(Debug)]
pub enum ThumbnailTaskInputMimeType {
MP4
}
pub enum ThumbnailTaskOutputMimeType {
PNG,
JPEG,
GIF,
}
impl ThumbnailTaskInputMimeType {
fn as_str(&self) -> &str {
match self {
ThumbnailTaskInputMimeType::MP4 => "video/mp4",
}
}
fn as_string(&self) -> String {
self.as_str().to_string()
}
fn to_output_mimetypes(&self) -> Vec<ThumbnailTaskOutputMimeType> {
match self {
ThumbnailTaskInputMimeType::MP4 => vec![
ThumbnailTaskOutputMimeType::JPEG,
ThumbnailTaskOutputMimeType::GIF,
],
}
}
}
impl ThumbnailTaskOutputMimeType {
fn as_str(&self) -> &str {
match self {
ThumbnailTaskOutputMimeType::PNG => "image/png",
ThumbnailTaskOutputMimeType::JPEG => "image/jpeg",
ThumbnailTaskOutputMimeType::GIF => "image/gif",
}
}
fn as_string(&self) -> String {
self.as_str().to_string()
}
fn to_extension(&self) -> &str {
match self {
ThumbnailTaskOutputMimeType::PNG => "png",
ThumbnailTaskOutputMimeType::JPEG => "jpg",
ThumbnailTaskOutputMimeType::GIF => "gif",
}
}
}
#[derive(Debug)]
pub enum ThumbnailTaskError {
InvalidTask,
@@ -75,20 +19,22 @@ struct ThumbnailTask{
#[derive(Debug)]
pub struct ThumbnailTaskBuilder{
source_mimetype: ThumbnailTaskInputMimeType,
bucket: Option<String>,
path: Option<String>,
source_mimetype: Option<String>,
output_mimetype: Option<String>,
output_extension: Option<String>,
output_suffix: Option<String>,
event_id: Option<String>,
}
impl ThumbnailTaskBuilder {
pub fn new_for_source_mimetype(source_mimetype: ThumbnailTaskInputMimeType) -> Self {
pub fn new() -> Self {
Self {
source_mimetype,
bucket: None,
path: None,
source_mimetype: None,
output_mimetype: None,
output_extension: None,
output_suffix: None,
event_id: None,
@@ -105,6 +51,21 @@ impl ThumbnailTaskBuilder {
self
}
pub fn with_source_mimetype(&mut self, source_mimetype: &str) -> &mut Self {
self.source_mimetype = Some(source_mimetype.to_string());
self
}
pub fn with_output_mimetype(&mut self, output_mimetype: &str) -> &mut Self {
self.output_mimetype = Some(output_mimetype.to_string());
self
}
pub fn with_output_extension(&mut self, output_extension: &str) -> &mut Self {
self.output_extension = Some(output_extension.to_string());
self
}
pub fn with_output_suffix(&mut self, output_suffix: &str) -> &mut Self {
self.output_suffix = Some(output_suffix.to_string());
self
@@ -115,43 +76,30 @@ impl ThumbnailTaskBuilder {
self
}
fn build(&self, thumbnail_task_output_mime_type: ThumbnailTaskOutputMimeType) -> Result<ThumbnailTask, String> {
fn build(&self) -> Result<ThumbnailTask, String> {
Ok(ThumbnailTask {
source_mimetype: self.source_mimetype.as_string(),
output_mimetype: thumbnail_task_output_mime_type.as_string(),
output_extension: thumbnail_task_output_mime_type.to_extension().to_string(),
bucket: self.bucket.clone().ok_or("bucket is required")?,
path: self.path.clone().ok_or("path is required")?,
source_mimetype: self.source_mimetype.clone().ok_or("source_mimetype is required")?,
output_mimetype: self.output_mimetype.clone().ok_or("output_mimetype is required")?,
output_extension: self.output_extension.clone().ok_or("output_extension is required")?,
output_suffix: self.output_suffix.clone().unwrap_or("".to_string()),
event_id: self.event_id.clone().ok_or("event_id is required")?,
})
}
pub async fn send_all(&self) -> Result<(), ThumbnailTaskError> {
let mut results: Vec<Result<(), ThumbnailTaskError>> = vec![];
for output_mimetype in self.source_mimetype.to_output_mimetypes() {
match self.build(output_mimetype) {
Ok(task) => {
match task.send().await {
Ok(_) => {},
Err(err) => {
error!("Failed to send thumbnail task: {:?} with err {:?}", self, err);
results.push(Err(ThumbnailTaskError::RequestError));
},
}
pub async fn send(&self) -> Result<(), ThumbnailTaskError> {
match self.build() {
Ok(task) => {
match task.send().await {
Ok(_) => Ok(()),
Err(_) => Err(ThumbnailTaskError::RequestError),
}
Err(err) => {
error!("Invalid thumbnail task: {:?} with err {:?}", self, err);
results.push(Err(ThumbnailTaskError::InvalidTask));
},
};
}
if results.iter().any(|r| r.is_err()) {
Err(ThumbnailTaskError::RequestError)
} else {
Ok(())
}
Err(_) => {
error!("Invalid thumbnail task: {:?}", self);
Err(ThumbnailTaskError::InvalidTask)
},
}
}
}
@@ -195,15 +143,9 @@ impl ThumbnailTask{
"http://media-server.thumbnail-generator/tasks",
);
let basic_auth = easyenv::get_env_string_or_default(
"THUMBNAIL_GENERATOR_API_BASIC_AUTH",
"",
);
let client = reqwest::Client::new();
let task_json = self.request_json();
client.post(url)
.header("Authorization", "Basic ".to_owned() + &basic_auth)
.json(&task_json)
.send()
.await?
@@ -0,0 +1,39 @@
[package]
name = "twitch_common"
edition = "2018"
version = "0.0.1"
authors = [
"Brandon Thomas <bt@brand.io>",
"Brandon Thomas <echelon@gmail.com>",
]
publish = false
[lib]
name = "twitch_common"
path = "src/lib.rs"
[package.metadata.cargo-udeps.ignore]
normal = ["my-workspace-hack"]
[dependencies]
# Internal
container_common = { path = "../../deprecated/container_common" }
easyenv = { path = "../../easyenv" }
# Workspace hack (faster compile times)
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
# External
anyhow = "1.0.86"
once_cell = "1.18.0"
regex = "1.5.4"
# Twitch
# NB "unsupported" pubsub features may disappear from the Twitch API at any time. It's an unofficial API.
# twitch_api2 = { version = "0.6.0-rc.2", features = ["helix", "reqwest_client", "pubsub", "chrono", "unsupported"] }
twitch_oauth2 = "0.6.0-rc.2"
# Serialization
serde = "1.0.125"
serde_derive = "1.0.125"
@@ -0,0 +1,107 @@
use std::collections::HashSet;
use once_cell::sync::Lazy;
use regex::Regex;
// NB: This can be used as a nice, succinct macro with once_cell:
//macro_rules! regex {
// ($re:literal $(,)?) => {{
// static RE: once_cell::sync::OnceCell<regex::Regex> = once_cell::sync::OnceCell::new();
// RE.get_or_init(|| regex::Regex::new($re).unwrap())
// }};
//}
/// Remove cheermotes from text, eg "this is great Cheer1" -> "this is great"
pub fn remove_cheers(text: &str) -> String {
let result = TWITCH_CHEER_REGEX.replace_all(text, "");
let result = MULTI_SPACE_REGEX.replace_all(&result, " ").to_string();
result.trim().to_string()
}
/// These cheer emotes always combine with a number
/// This list seems pretty comprehensive
/// https://github.com/nossebro/TwitchPubSubMirror/blob/master/TwitchPubSubMirror_StreamlabsSystem.py
static TWITCH_CHEERS : Lazy<HashSet<String>> = Lazy::new(|| {
let cheers = vec![
"Cheer",
"DoodleCheer",
"BibleThump",
"cheerwhal",
"Corgo",
"uni",
"ShowLove",
"Party",
"SeemsGood",
"Pride",
"Kappa",
"FrankerZ",
"HeyGuys",
"DansGame",
"EleGiggle",
"TriHard",
"Kreygasm",
"4Head",
"SwiftRage",
"NotLikeThis",
"FailFish",
"VoHiYo",
"PJSalt",
"MrDestructoid",
"bday",
"RIPCheer",
"Shamrock",
"BitBoss",
"Streamlabs",
"Muxy",
"HolidayCheer"
];
let mut s = HashSet::new();
for cheer in cheers.into_iter() {
s.insert(cheer.to_string());
}
s
});
static MULTI_SPACE_REGEX : Lazy<Regex> = Lazy::new(|| {
Regex::new("(\\s{2,})").unwrap()
});
static TWITCH_CHEER_REGEX : Lazy<Regex> = Lazy::new(|| {
let mut regex_pieces = Vec::new();
for cheer in TWITCH_CHEERS.iter() {
regex_pieces.push(format!("({}\\d+)", cheer));
}
let regex = regex_pieces.join("|");
// NB: (?i) is case insensitive search
// https://docs.rs/regex/1.5.4/regex/#structs
let regex = format!("(?i)({})", regex);
Regex::new(&regex).unwrap()
});
#[cfg(test)]
mod tests {
use crate::cheers::remove_cheers;
#[test]
fn test_remove_cheers() {
let r = remove_cheers("testing Kappa1 SeemsGood100 removal");
assert_eq!(&r, "testing removal");
let r = remove_cheers("Kappa1 SeemsGood100 no more Cheer1 cheers");
assert_eq!(&r, "no more cheers");
}
#[test]
fn test_remove_cheers_case_insensitive() {
let r = remove_cheers("testing cheer1");
assert_eq!(&r, "testing");
let r = remove_cheers("TESTING CHEER1");
assert_eq!(&r, "TESTING");
}
}
@@ -0,0 +1,4 @@
pub mod cheers;
pub mod oauth_token_builder;
pub mod twitch_secrets;
pub mod twitch_user_id;
@@ -0,0 +1,66 @@
use twitch_oauth2::{ClientId, ClientSecret, Scope};
use twitch_oauth2::tokens::UserTokenBuilder;
use twitch_oauth2::url::Url;
/// Get the token builder with the scopes our app needs
/// This is used in several places, so centralize creation here.
pub fn get_oauth_token_builder(
client_id: &str,
client_secret: &str,
redirect_url: &Url,
force_verify: bool
) -> UserTokenBuilder {
let client_id = ClientId::new(client_id);
let client_secret = ClientSecret::new(client_secret);
/*
- AnalyticsReadExtensions, scope: "analytics:read:extensions", doc: "View analytics data for the Twitch Extensions owned by the authenticated account.";
- AnalyticsReadGames, scope: "analytics:read:games", doc: "View analytics data for the games owned by the authenticated account.";
[YES] BitsRead, scope: "bits:read", doc: "View Bits information for a channel.";
- ChannelEditCommercial, scope: "channel:edit:commercial", doc: "Run commercials on a channel.";
- ChannelManageBroadcast, scope: "channel:manage:broadcast", doc: "Manage a channel’s broadcast configuration, including updating channel configuration and managing stream markers and stream tags.";
- ChannelManageExtensions, scope: "channel:manage:extensions", doc: "Manage a channel’s Extension configuration, including activating Extensions.";
(?) ChannelManagePolls, scope: "channel:manage:polls", doc: "Manage a channel’s polls.";
(?) ChannelManagePredictions, scope: "channel:manage:predictions", doc: "Manage of channel’s Channel Points Predictions";
- ChannelManageRedemptions, scope: "channel:manage:redemptions", doc: "Manage Channel Points custom rewards and their redemptions on a channel.";
- ChannelManageSchedule, scope: "channel:manage:schedule", doc: "Manage a channel’s stream schedule.";
- ChannelManageVideos, scope: "channel:manage:videos", doc: "Manage a channel’s videos, including deleting videos.";
- ChannelModerate, scope: "channel:moderate", doc: "Perform moderation actions in a channel. The user requesting the scope must be a moderator in the channel.";
- ChannelReadEditors, scope: "channel:read:editors", doc: "View a list of users with the editor role for a channel.";
- ChannelReadGoals, scope: "channel:read:goals", doc: "View Creator Goals for a channel.";
- ChannelReadHypeTrain, scope: "channel:read:hype_train", doc: "View Hype Train information for a channel.";
(?) ChannelReadPolls, scope: "channel:read:polls", doc: "View a channel’s polls.";
(?) ChannelReadPredictions, scope: "channel:read:predictions", doc: "View a channel’s Channel Points Predictions.";
+ ChannelReadRedemptions, scope: "channel:read:redemptions", doc: "View Channel Points custom rewards and their redemptions on a channel.";
- ChannelReadStreamKey, scope: "channel:read:stream_key", doc: "View an authorized user’s stream key.";
+ ChannelReadSubscriptions, scope: "channel:read:subscriptions", doc: "View a list of all subscribers to a channel and check if a user is subscribed to a channel.";
- ChannelSubscriptions, scope: "channel_subscriptions", doc: "\\[DEPRECATED\\] Read all subscribers to your channel.";
[YES] ChatEdit, scope: "chat:edit", doc: "Send live stream chat and rooms messages.";
[YES] ChatRead, scope: "chat:read", doc: "View live stream chat and rooms messages.";
(?) ClipsEdit, scope: "clips:edit", doc: "Manage Clips for a channel.";
(?) ModerationRead, scope: "moderation:read", doc: "View a channel’s moderation data including Moderators, Bans, Timeouts, and Automod settings.";
(?) ModeratorManageAutoMod, scope: "moderator:manage:automod", doc: "Manage messages held for review by AutoMod in channels where you are a moderator.";
- UserEdit, scope: "user:edit", doc: "Manage a user object.";
- UserEditBroadcast, scope: "user:edit:broadcast", doc: "Edit your channel's broadcast configuration, including extension configuration. (This scope implies user:read:broadcast capability.)";
- UserEditFollows, scope: "user:edit:follows", doc: "Edit a user’s follows.";
- UserManageBlockedUsers, scope: "user:manage:blocked_users", doc: "Manage the block list of a user.";
- UserReadBlockedUsers, scope: "user:read:blocked_users", doc: "View the block list of a user.";
- UserReadBroadcast, scope: "user:read:broadcast", doc: "View a user’s broadcasting configuration, including Extension configurations.";
- UserReadEmail, scope: "user:read:email", doc: "Read an authorized user’s email address.";
- UserReadFollows, scope: "user:read:follows", doc: "View the list of channels a user follows.";
(?) UserReadSubscriptions, scope: "user:read:subscriptions", doc: "View if an authorized user is subscribed to specific channels.";
- WhispersEdit, scope: "whispers:edit", doc: "Send whisper messages.";
- WhispersRead, scope: "whispers:read", doc: "View your whisper messages.";
*/
UserTokenBuilder::new(client_id, client_secret, redirect_url.clone())
.set_scopes(vec![
Scope::BitsRead, // Using bits to cheer (cost money, larger streams)
Scope::ChannelReadRedemptions, // Channel points redeemed (free points, smaller streams)
Scope::ChannelReadSubscriptions, // List subscribing (paying) users
Scope::ChatEdit, // IRC send
Scope::ChatRead, // IRC read
])
.force_verify(force_verify)
}
@@ -0,0 +1,29 @@
use container_common::anyhow_result::AnyhowResult;
/// Standardized way to read Twitch secrets.
/// These are used in several different apps.
#[derive(Clone, serde::Deserialize)]
pub struct TwitchSecrets {
pub app_client_id: String,
pub app_client_secret: String,
}
impl TwitchSecrets {
pub fn new(
app_client_id: &str,
app_client_secret: &str,
) -> Self {
Self {
app_client_id: app_client_id.to_string(),
app_client_secret: app_client_secret.to_string(),
}
}
pub fn from_env() -> AnyhowResult<Self> {
Ok(Self::new(
&easyenv::get_env_string_required("TWITCH_APP_CLIENT_ID")?,
&easyenv::get_env_string_required("TWITCH_APP_CLIENT_SECRET")?,
))
}
}
@@ -0,0 +1,35 @@
use container_common::anyhow_result::AnyhowResult;
/// Twitch says to encode these as strings, but the Twitch Rust libraries
/// still deal in integers (uh oh). This centralizes type juggling.
#[derive(Clone)]
pub struct TwitchUserId {
string_user_id: String,
numeric_user_id: u32,
}
impl TwitchUserId {
pub fn from_str(twitch_user_id: &str) -> AnyhowResult<Self> {
let numeric_id = twitch_user_id.parse::<u32>()?;
Ok(Self {
string_user_id: twitch_user_id.to_string(),
numeric_user_id: numeric_id,
})
}
pub fn from_numeric(twitch_user_id: u32) -> Self {
Self {
string_user_id: twitch_user_id.to_string(),
numeric_user_id: twitch_user_id,
}
}
pub fn get_numeric(&self) -> u32 {
self.numeric_user_id
}
pub fn get_str(&self) -> &str {
&self.string_user_id
}
}
+1 -1
View File
@@ -27,7 +27,7 @@ my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
# External
dotenv = "0.15.0"
hostname = "0.4.0"
hostname = "0.3.1"
log = "0.4.14"
[dev-dependencies]
@@ -28,4 +28,3 @@ anyhow = "1.0.86"
# In-memory caching
lru_time_cache = "0.11.11"
sieve-cache = "0.2.1"
@@ -1,69 +0,0 @@
use std::hash::Hash;
use std::sync::{Arc, Mutex};
use anyhow::{anyhow, bail};
use sieve_cache::SieveCache;
use errors::AnyhowResult;
#[derive(Clone)]
pub struct ArcSieve<K: Eq + Hash + Clone, V: Clone + ?Sized> {
cache: Arc<Mutex<SieveCache<K, V>>>,
}
impl <K: Eq + Hash + Clone, V: Clone + ?Sized> ArcSieve<K, V> {
pub fn with_capacity(capacity: usize) -> AnyhowResult<Self> {
let cache = SieveCache::new(capacity)
.map_err(|err| anyhow!("could not create cache: {:?}", err))?;
Ok(Self {
cache: Arc::new(Mutex::new(cache)),
})
}
pub fn store(&self, key: K, value: V) -> AnyhowResult<()> {
match self.cache.lock() {
Err(e) => bail!("could not unlock mutex to write: {:?}", e),
Ok(mut cache) => {
cache.insert(key, value);
},
};
Ok(())
}
pub fn store_copy(&self, key: &K, value: &V) -> AnyhowResult<()> {
match self.cache.lock() {
Err(e) => bail!("could not unlock mutex to write: {:?}", e),
Ok(mut cache) => {
cache.insert(key.clone(), value.clone());
},
};
Ok(())
}
pub fn get_copy(&self, key: &K) -> AnyhowResult<Option<V>> {
let maybe_copy = match self.cache.lock() {
Err(e) => bail!("could not unlock mutex to read: {:?}", e),
Ok(mut cache) => {
cache.get(key).map(|inner| inner.clone())
},
};
Ok(maybe_copy)
}
}
#[cfg(test)]
mod tests {
use crate::arc_sieve::ArcSieve;
#[test]
fn test_get_copy() {
let sieve : ArcSieve<String, String> = ArcSieve::with_capacity(1).unwrap();
let key = "key".to_string();
let value = "value".to_string();
sieve.store_copy(&key, &value).unwrap();
let maybe_value = sieve.get_copy(&key).unwrap();
assert_eq!(maybe_value, Some("value".to_string()));
}
}
@@ -14,7 +14,6 @@
#![allow(dead_code)]
#![allow(non_snake_case)]
pub mod arc_sieve;
pub mod multi_item_ttl_cache;
pub mod single_item_ttl_cache;
pub mod ttl_key_counter;
+2 -2
View File
@@ -24,9 +24,9 @@ errors = { path = "../errors" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
# External
hmac = "0.10.1" # jwt signing
hmac = "0.10.0" # jwt signing
jwt = "0.13.0"
sha2 = "0.9.9" # hmac signing
sha2 = "0.9.5" # hmac signing
[dev-dependencies]
# None yet

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