Compare commits

...
Author SHA1 Message Date
Michael Chung fd95aaff8c Merge branch 'feature/weights_endpoints' of https://github.com/storytold/storyteller-rust into feature/weights_endpoints 2023-12-07 20:02:02 -05:00
Michael Chung 8afd10c864 minor fixes to spelling 2023-12-07 17:52:21 -05:00
ArEnSc 0e62fe9d08 Merge branch 'master' into feature/weights_endpoints 2023-12-07 00:35:53 -05:00
Michael Chung dda2e0840d Updated docs and weights_types 2023-12-07 00:25:57 -05:00
Michael Chung b26276cfda done now for code 2023-12-06 23:36:20 -05:00
Brandon Thomas eedbc346fe Merge pull request #194 from storytold/brandon-tts-as-media-file
Save TTS results as media files
2023-12-06 23:33:51 -05:00
Brandon Thomas 7976f68fd0 Save TTS results as media files 2023-12-06 23:31:47 -05:00
Michael Chung f327dfcdbb Builds and docs work 2023-12-06 23:27:34 -05:00
Michael Chung f4176bf84a Alright should fix the generation now... 2023-12-06 21:31:37 -05:00
Michael Chung 43b6808f23 Ok fixed docs and avaoilable weights endpoint 2023-12-06 20:05:23 -05:00
Michael Chung 330f24413d added missing file 2023-12-06 19:54:21 -05:00
Michael Chung af6370eea1 Issue with the api doc not sure. 2023-12-06 19:53:52 -05:00
Michael Chung 3a4ca7b548 weights doc complete. 2023-12-06 16:14:08 -05:00
Michael Chung f7d5ad496c Update docs 2023-12-06 14:01:31 -05:00
Brandon Thomas 8a6678c7f5 move workspace-hack under crates/ 2023-12-06 02:12:57 -05:00
Michael Chung f76928d001 Delete weight documentation added 2023-12-06 01:28:54 -05:00
Michael Chung 32cfb10323 added weights list documentation 2023-12-06 00:55:28 -05:00
Brandon Thomas d9b1c8c36b Merge pull request #192 from storytold/brandon-upgrade-chrono-vendored-limitation
upgrade chrono with vendored limitation
2023-12-05 21:54:36 -05:00
Brandon Thomas fc122cb996 upgrade chrono with vendored limitation 2023-12-05 21:54:06 -05:00
Michael Chung 08d3c389b9 Merge branch 'master' into feature/weights_endpoints 2023-12-05 21:48:14 -05:00
Michael Chung 1a3b02b5d2 Adding docs 2023-12-05 21:46:53 -05:00
Brandon Thomas e0e85fed5f Merge pull request #191 from storytold/brandon-vendor-limitation
vendor `limitation` crate
2023-12-05 20:58:27 -05:00
Brandon Thomas 092745166e vendor limitation crate
The limitation crate is four years old, stuck at 0.1.1. It looks unlikey
to be updated by the maintainer.

Upgrades to chrono and other libraries have broken limitation, so we're
likely now on the hook for maintaining this.
2023-12-05 20:56:22 -05:00
Michael Chung 024b4d769c Merge branch 'master' into feature/weights_endpoints 2023-12-05 20:41:44 -05:00
Brandon Thomas ea859bfc1e Merge pull request #190 from storytold/brandon-revert-chrono-upgrade
Revert "Bump chrono from 0.4.22 to 0.4.31"
2023-12-05 20:40:37 -05:00
Brandon Thomas 92e0b1e9f4 Revert "Bump chrono from 0.4.22 to 0.4.31"
This reverts commit 4856ac28aa.
2023-12-05 20:40:09 -05:00
ArEnSc d52d3f993a Merge pull request #189 from storytold/feature/api-spec
Feature/api spec
2023-12-05 20:27:57 -05:00
Brandon Thomas 514f80b884 Merge pull request #184 from storytold/dependabot/cargo/rustix-0.38.26
Bump rustix from 0.38.25 to 0.38.26
2023-12-05 19:22:24 -05:00
Brandon Thomas 74c548c02f Merge pull request #182 from storytold/dependabot/cargo/chrono-0.4.31
Bump chrono from 0.4.22 to 0.4.31
2023-12-05 19:21:53 -05:00
Brandon Thomas 5fe09a182f Merge pull request #185 from storytold/dependabot/cargo/lettre-0.11.2
Bump lettre from 0.11.1 to 0.11.2
2023-12-05 19:21:40 -05:00
dependabot[bot] 4856ac28aa Bump chrono from 0.4.22 to 0.4.31
Bumps [chrono](https://github.com/chronotope/chrono) from 0.4.22 to 0.4.31.
- [Release notes](https://github.com/chronotope/chrono/releases)
- [Changelog](https://github.com/chronotope/chrono/blob/main/CHANGELOG.md)
- [Commits](https://github.com/chronotope/chrono/compare/v0.4.22...v0.4.31)

---
updated-dependencies:
- dependency-name: chrono
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-06 00:19:36 +00:00
Brandon Thomas 404853e129 cleanup misleading old comment 2023-12-05 19:16:55 -05:00
Brandon Thomas 300ac56ab7 Merge pull request #188 from storytold/brandon-media-files-global-list-fixed
global list of media files
2023-12-05 19:11:20 -05:00
Brandon Thomas e32f8094e5 global list of media files 2023-12-05 19:09:42 -05:00
Brandon Thomas 642e17a77d Merge pull request #187 from storytold/brandon-fix-sqlx-query-builder
Fix QueryBuilder with respect to column names and type juggling
2023-12-05 18:31:13 -05:00
Brandon Thomas 36f3003576 Fix QueryBuilder with respect to column names and type juggling 2023-12-05 18:30:26 -05:00
Michael Chung 4993410740 Built needs a simple endpoint query test 2023-12-05 14:54:32 -05:00
Michael Chung f99b5a92b8 Added weights paging to michael's spec missing the fields. 2023-12-05 13:44:50 -05:00
Brandon Thomas b9f604dd18 reusable pagination types 2023-12-04 23:11:41 -05:00
Michael Chung 130e0daa08 paging works correctly. 2023-12-04 22:49:33 -05:00
Brandon Thomas e6f5ecb980 Merge pull request #186 from storytold/brandon-list-media-files-for-user
list media files for user
2023-12-04 22:02:53 -05:00
Brandon Thomas 2e3ff8a730 list media files for user 2023-12-04 21:59:39 -05:00
Michael Chung cda690773f Merge branch 'master' into feature/weights_endpoints 2023-12-04 18:26:24 -05:00
Michael Chung 91d0a1da8f Tests pass again 2023-12-04 18:21:43 -05:00
Michael Chung 8c7b100150 Adding new seed then have to switch tests back. 2023-12-04 18:06:11 -05:00
dependabot[bot] ac13be2900 Bump lettre from 0.11.1 to 0.11.2
Bumps [lettre](https://github.com/lettre/lettre) from 0.11.1 to 0.11.2.
- [Release notes](https://github.com/lettre/lettre/releases)
- [Changelog](https://github.com/lettre/lettre/blob/master/CHANGELOG.md)
- [Commits](https://github.com/lettre/lettre/compare/v0.11.1...v0.11.2)

---
updated-dependencies:
- dependency-name: lettre
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-04 22:30:26 +00:00
dependabot[bot] b55435d992 Bump rustix from 0.38.25 to 0.38.26
Bumps [rustix](https://github.com/bytecodealliance/rustix) from 0.38.25 to 0.38.26.
- [Release notes](https://github.com/bytecodealliance/rustix/releases)
- [Commits](https://github.com/bytecodealliance/rustix/compare/v0.38.25...v0.38.26)

---
updated-dependencies:
- dependency-name: rustix
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-04 22:30:06 +00:00
Michael Chung ae5eea53fe Something wrong with the query parameters not passing off the limits etc 2023-12-04 03:57:40 -05:00
Michael Chung 241f648baf Add type filtering in there and category filtering 2023-12-04 03:38:51 -05:00
Michael Chung e4756a4f2c Added more data seeding to the database for the model_weights table. 2023-12-04 01:52:14 -05:00
Michael Chung 9c8ee8dbbb Ok list_weights_by_user works properly now. 2023-12-03 22:55:56 -05:00
Michael Chung 8072d13596 Fixed Delete and Updates for Weights 2023-12-03 01:35:04 -05:00
Michael Chung 1d937ab981 Initial weights_endpoints need testing right now. 2023-12-01 15:22:32 -05:00
Michael Chung 496ccd68f1 Working on uploads gonna be testing the endpoints soon.
Need a ui for the upload page to understand how to write it but it's similar to prior art just need to check that out.
2023-11-30 23:05:02 -05:00
Michael Chung 72fdd75568 added upload weights / create weight endpoint
original file for the weights
weights file type for safetensors or checkpoint
Gotta add one more thing for record creation
2023-11-30 17:33:25 -05:00
Michael Chung fba7d2a1eb needs some tests for users and query builds with pagination 2023-11-30 00:48:54 -05:00
Michael Chung 347fe685cd List list_availible_weights needs testing but is written. 2023-11-29 23:11:47 -05:00
Michael Chung 32331f9890 compiles working on list queries 2023-11-29 21:46:42 -05:00
Michael Chung 65d6f178bc WIP - weights endpoints need to be tested
Update weights endpoints had to be fixed.
should be ready to unit test the 3 endpoints before using the list weights
2023-11-29 17:07:18 -05:00
Michael Chung d9d166e500 Run routes using open api spec 2023-11-28 17:27:32 -05:00
156 changed files with 6300 additions and 706 deletions
Generated
+529 -221
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -86,8 +86,9 @@ members = [
"crates/service/plugins/users",
# Vendored code
"crates/vendor/limitation",
"crates/vendor/newrelic-telemetry-sdk-rust",
# Vendored code
"my-workspace-hack",
# cargo-hakari workspace hack (compilation speedup hack)
"crates/my-workspace-hack",
]
+1 -1
View File
@@ -17,7 +17,7 @@ path = "src/lib.rs"
# Internal
crockford = { path = "../../lib/crockford" }
errors = { path = "../../lib/errors" }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
# External
# (none yet)
+2 -1
View File
@@ -2,4 +2,5 @@ pub mod media_files;
pub mod media_uploads;
pub mod public_path;
pub mod voice_conversion_results;
pub mod zs_voices;
pub mod zs_voices;
pub mod weight_uploads;
@@ -0,0 +1,77 @@
use crockford::crockford_entropy_lower;
// TODO Thoughts NOTE:
// I am wondering if it be easier if we just had a single struct and impl to handle downloads from public and private buckets.
// essentially, the only difference is the prefix and file postfix and the path of where the file goes and the path will be constructed from there.
// then you can tie in the downloader as a single object that can be used for both public and private buckets.
use crate::public::public_path::PublicPath;
use crate::util::hashed_directory_path_long_string::hashed_directory_path_long_string;
const WEIGHT_UPLOAD_DIRECTORY : &str = "/weight_upload";
/// Directory for user media uploads.
/// Each uploaded file gets its own directory so that we can store the original
/// file alongside re-processed derivatives.
#[derive(Clone)]
pub struct WeightUploadDirectory {
object_hash: String,
directory: String,
}
impl PublicPath for WeightUploadDirectory {}
impl WeightUploadDirectory {
pub fn generate_new() -> Self {
let entropy = crockford_entropy_lower(32);
Self::from_object_hash(&entropy)
}
pub fn from_object_hash(object_hash: &str) -> Self {
// TODO: Path construction could be cleaner.
let middle = hashed_directory_path_long_string(object_hash);
let directory = format!("{}/{}{}", WEIGHT_UPLOAD_DIRECTORY, middle, object_hash);
Self {
object_hash: object_hash.to_string(),
directory,
}
}
pub fn get_directory_path_str(&self) -> &str {
&self.directory
}
pub fn get_object_hash(&self) -> &str {
&self.object_hash
}
}
#[cfg(test)]
mod tests {
use crate::public::weight_uploads::directory::WeightUploadDirectory;
#[test]
pub fn generate_new_entropy() {
let directory = WeightUploadDirectory::generate_new();
assert_eq!(directory.get_object_hash().len(), 32);
}
#[test]
pub fn get_directory_path_str() {
let directory = WeightUploadDirectory::from_object_hash("abcdefghijk");
assert_eq!(directory.get_directory_path_str(), "/weight_upload/a/b/c/d/e/abcdefghijk");
}
#[test]
pub fn get_directory_path_str_short_name() {
let directory = WeightUploadDirectory::from_object_hash("foo");
assert_eq!(directory.get_directory_path_str(), "/weight_upload/f/o/foo");
}
#[test]
pub fn get_object_hash() {
let hash = "abcdefghijk";
let directory = WeightUploadDirectory::from_object_hash(hash);
assert_eq!(directory.get_object_hash(), hash);
}
}
@@ -0,0 +1,2 @@
pub mod directory;
pub mod original_file;
@@ -0,0 +1,99 @@
use std::path::PathBuf;
use crockford::crockford_entropy_lower;
use crate::public::weight_uploads::directory::WeightUploadDirectory;
use crate::public::public_path::PublicPath;
const ORIGINAL_FILE_BASENAME : &str = "original_upload.bin";
#[derive(Clone)]
pub struct WeightUploadOriginalFilePath {
directory: WeightUploadDirectory,
full_object_path: String,
}
impl PublicPath for WeightUploadOriginalFilePath {}
impl WeightUploadOriginalFilePath {
pub fn generate_new() -> Self {
let entropy = crockford_entropy_lower(32);
Self::from_object_hash(&entropy)
}
pub fn from_object_hash(hash: &str) -> Self {
// TODO: Path construction could be cleaner.
let directory = WeightUploadDirectory::from_object_hash(hash);
let full_object_path = format!("{}/{}", directory.get_directory_path_str(), ORIGINAL_FILE_BASENAME);
Self {
directory,
full_object_path,
}
}
pub fn get_full_object_path_str(&self) -> &str {
&self.full_object_path
}
pub fn to_full_object_pathbuf(&self) -> PathBuf {
PathBuf::from(&self.full_object_path)
}
pub fn get_directory(&self) -> &WeightUploadDirectory {
&self.directory
}
pub fn get_object_hash(&self) -> &str {
&self.directory.get_object_hash()
}
pub fn get_basename() -> &'static str {
ORIGINAL_FILE_BASENAME
}
}
#[cfg(test)]
mod tests {
use std::path::PathBuf;
use crate::public::weight_uploads::original_file::WeightUploadOriginalFilePath;
#[test]
pub fn generate_new_entropy() {
let file = WeightUploadOriginalFilePath::generate_new();
assert_eq!(file.get_object_hash().len(), 32);
assert_eq!(file.get_directory().get_object_hash().len(), 32);
}
#[test]
pub fn get_full_object_path_str() {
let file = WeightUploadOriginalFilePath::from_object_hash("abcdefghijk");
assert_eq!(file.get_full_object_path_str(), "/weight_upload/a/b/c/d/e/abcdefghijk/original_upload.bin");
}
#[test]
pub fn to_full_object_pathbuf() {
let file = WeightUploadOriginalFilePath::from_object_hash("abcdefghijk");
assert_eq!(file.to_full_object_pathbuf(), PathBuf::from("/weight_upload/a/b/c/d/e/abcdefghijk/original_upload.bin"));
}
#[test]
pub fn get_full_object_path_str_short_name() {
let file = WeightUploadOriginalFilePath::from_object_hash("foo");
assert_eq!(file.get_full_object_path_str(), "/weight_upload/f/o/foo/original_upload.bin");
}
#[test]
pub fn get_full_object_path_str_starts_with_directory() {
let file = WeightUploadOriginalFilePath::from_object_hash("abcdefghijk");
let full_path = file.get_full_object_path_str();
assert!(full_path.starts_with(file.get_directory().get_directory_path_str()))
}
#[test]
pub fn get_object_hash() {
let hash = "abcdefghijk";
let file = WeightUploadOriginalFilePath::from_object_hash(hash);
assert_eq!(file.get_object_hash(), hash);
}
}
+2 -4
View File
@@ -17,14 +17,12 @@ path = "src/lib.rs"
serde = "1.0.125"
serde_derive = "1.0.125"
# MySql Database
# "offline" supports typesafe builds without a live database
# "rustls" doesn't appear to work with IP addresses, per:
# https://github.com/launchbadge/sqlx/issues/1095#issuecomment-798050828
sqlx = { version = "0.7.2", features = [ "mysql", "sqlite", "runtime-tokio-native-tls", "chrono" ] }
sqlx-core = { version = "0.7.2" }
sqlx-mysql= { version = "0.7.2" }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
utoipa = { version = "4", features = ["chrono","actix_extras"] }
[dev-dependencies]
@@ -13,10 +13,11 @@ use strum::EnumIter;
///
/// DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY.
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize, Default)]
pub enum InferenceCategory {
/// Eg. SadTalker and possibly Wav2Lip
#[serde(rename = "lipsync_animation")]
#[default]
LipsyncAnimation,
#[serde(rename = "text_to_speech")]
@@ -21,6 +21,11 @@ pub enum IdCategory {
#[serde(rename = "lipsync_animation")]
LipsyncAnimationResult,
/// Results from tacotron2
/// Applies for RVC and SVC
#[serde(rename = "tts_result")]
TtsResult,
/// Results from voice conversion (which may live in the media_files table)
/// Applies for RVC and SVC
#[serde(rename = "voice_conversion")]
@@ -52,6 +57,7 @@ impl IdCategory {
match self {
Self::MediaFile => "media_file",
Self::LipsyncAnimationResult => "lipsync_animation",
Self::TtsResult => "tts_result",
Self::VoiceConversionResult => "voice_conversion",
Self::ZeroShotVoiceDataset => "zs_dataset",
Self::ZeroShotVoiceEmbedding => "zs_voice",
@@ -64,6 +70,7 @@ impl IdCategory {
match value {
"media_file" => Ok(Self::MediaFile),
"lipsync_animation" => Ok(Self::LipsyncAnimationResult),
"tts_result" => Ok(Self::TtsResult),
"voice_conversion" => Ok(Self::VoiceConversionResult),
"zs_dataset" => Ok(Self::ZeroShotVoiceDataset),
"zs_voice" => Ok(Self::ZeroShotVoiceEmbedding),
@@ -79,6 +86,7 @@ impl IdCategory {
BTreeSet::from([
Self::MediaFile,
Self::LipsyncAnimationResult,
Self::TtsResult,
Self::VoiceConversionResult,
Self::ZeroShotTtsResult,
Self::ZeroShotVoiceDataset,
@@ -100,6 +108,7 @@ mod tests {
fn test_serialization() {
assert_serialization(IdCategory::MediaFile, "media_file");
assert_serialization(IdCategory::LipsyncAnimationResult, "lipsync_animation");
assert_serialization(IdCategory::TtsResult, "tts_result");
assert_serialization(IdCategory::VoiceConversionResult, "voice_conversion");
assert_serialization(IdCategory::ZeroShotVoiceDataset, "zs_dataset");
assert_serialization(IdCategory::ZeroShotVoiceEmbedding, "zs_voice");
@@ -111,6 +120,7 @@ mod tests {
fn to_str() {
assert_eq!(IdCategory::MediaFile.to_str(), "media_file");
assert_eq!(IdCategory::LipsyncAnimationResult.to_str(), "lipsync_animation");
assert_eq!(IdCategory::TtsResult.to_str(), "tts_result");
assert_eq!(IdCategory::VoiceConversionResult.to_str(), "voice_conversion");
assert_eq!(IdCategory::ZeroShotVoiceDataset.to_str(), "zs_dataset");
assert_eq!(IdCategory::ZeroShotVoiceEmbedding.to_str(), "zs_voice");
@@ -122,6 +132,7 @@ mod tests {
fn from_str() {
assert_eq!(IdCategory::from_str("media_file").unwrap(), IdCategory::MediaFile);
assert_eq!(IdCategory::from_str("lipsync_animation").unwrap(), IdCategory::LipsyncAnimationResult);
assert_eq!(IdCategory::from_str("tts_result").unwrap(), IdCategory::TtsResult);
assert_eq!(IdCategory::from_str("voice_conversion").unwrap(), IdCategory::VoiceConversionResult);
assert_eq!(IdCategory::from_str("zs_dataset").unwrap(), IdCategory::ZeroShotVoiceDataset);
assert_eq!(IdCategory::from_str("zs_voice").unwrap(), IdCategory::ZeroShotVoiceEmbedding);
@@ -133,9 +144,10 @@ mod tests {
fn all_variants() {
// Static check
let mut variants = IdCategory::all_variants();
assert_eq!(variants.len(), 7);
assert_eq!(variants.len(), 8);
assert_eq!(variants.pop_first(), Some(IdCategory::MediaFile));
assert_eq!(variants.pop_first(), Some(IdCategory::LipsyncAnimationResult));
assert_eq!(variants.pop_first(), Some(IdCategory::TtsResult));
assert_eq!(variants.pop_first(), Some(IdCategory::VoiceConversionResult));
assert_eq!(variants.pop_first(), Some(IdCategory::ZeroShotTtsResult));
assert_eq!(variants.pop_first(), Some(IdCategory::ZeroShotVoiceDataset));
@@ -1,4 +1,5 @@
use std::collections::BTreeSet;
use serde::Deserialize;
use serde::Serialize;
#[cfg(test)]
@@ -29,6 +30,7 @@ pub enum MediaFileOriginCategory {
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
impl_enum_display_and_debug_using_to_str!(MediaFileOriginCategory);
impl_mysql_enum_coders!(MediaFileOriginCategory);
impl_mysql_from_row!(MediaFileOriginCategory);
/// NB: Legacy API for older code.
impl MediaFileOriginCategory {
@@ -24,6 +24,10 @@ pub enum MediaFileOriginModelType {
/// so-vits-svc voice conversion models
#[serde(rename = "so_vits_svc")]
SoVitsSvc,
#[serde(rename = "tacotron2")]
Tacotron2,
#[serde(rename = "vall_e_x")]
VallEX,
}
@@ -31,6 +35,7 @@ pub enum MediaFileOriginModelType {
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
impl_enum_display_and_debug_using_to_str!(MediaFileOriginModelType);
impl_mysql_enum_coders!(MediaFileOriginModelType);
impl_mysql_from_row!(MediaFileOriginModelType);
/// NB: Legacy API for older code.
impl MediaFileOriginModelType {
@@ -39,6 +44,7 @@ impl MediaFileOriginModelType {
Self::RvcV2 => "rvc_v2",
Self::SadTalker => "sad_talker",
Self::SoVitsSvc => "so_vits_svc",
Self::Tacotron2 => "tacotron2",
Self::VallEX => "vall_e_x"
}
}
@@ -48,6 +54,7 @@ impl MediaFileOriginModelType {
"rvc_v2" => Ok(Self::RvcV2),
"sad_talker" => Ok(Self::SadTalker),
"so_vits_svc" => Ok(Self::SoVitsSvc),
"tacotron2" => Ok(Self::Tacotron2),
"vall_e_x" => Ok(Self::VallEX),
_ => Err(format!("invalid value: {:?}", value)),
}
@@ -60,6 +67,7 @@ impl MediaFileOriginModelType {
Self::RvcV2,
Self::SadTalker,
Self::SoVitsSvc,
Self::Tacotron2,
Self::VallEX
])
}
@@ -83,6 +91,7 @@ mod tests {
assert_eq!(MediaFileOriginModelType::RvcV2.to_str(), "rvc_v2");
assert_eq!(MediaFileOriginModelType::SadTalker.to_str(), "sad_talker");
assert_eq!(MediaFileOriginModelType::SoVitsSvc.to_str(), "so_vits_svc");
assert_eq!(MediaFileOriginModelType::Tacotron2.to_str(), "tacotron2");
assert_eq!(MediaFileOriginModelType::VallEX.to_str(), "vall_e_x");
}
@@ -91,17 +100,19 @@ mod tests {
assert_eq!(MediaFileOriginModelType::from_str("rvc_v2").unwrap(), MediaFileOriginModelType::RvcV2);
assert_eq!(MediaFileOriginModelType::from_str("sad_talker").unwrap(), MediaFileOriginModelType::SadTalker);
assert_eq!(MediaFileOriginModelType::from_str("so_vits_svc").unwrap(), MediaFileOriginModelType::SoVitsSvc);
assert_eq!(MediaFileOriginModelType::from_str("tacotron2").unwrap(), MediaFileOriginModelType::Tacotron2);
assert_eq!(MediaFileOriginModelType::from_str("vall_e_x").unwrap(), MediaFileOriginModelType::VallEX);
assert!(MediaFileOriginModelType::from_str("foo").is_err());
assert!(MediaFileOriginModelType::from_str("foo").is_err());
}
#[test]
fn all_variants() {
let mut variants = MediaFileOriginModelType::all_variants();
assert_eq!(variants.len(), 4);
assert_eq!(variants.len(), 5);
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::RvcV2));
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::SadTalker));
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::SoVitsSvc));
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::Tacotron2));
assert_eq!(variants.pop_first(), Some(MediaFileOriginModelType::VallEX));
assert_eq!(variants.pop_first(), None);
}
@@ -21,6 +21,10 @@ pub enum MediaFileOriginProductCategory {
#[serde(rename = "face_animator")]
FaceAnimator,
/// Text to speech (Tacotron2, not voice designer / VallE-X)
#[serde(rename = "tts")]
TextToSpeech,
// TODO: This should be a temporary category until we migrate the DB to remove this default value
/// Unknown which product is attached to the file (generated the file, the file was
/// uploaded on behalf of, etc.)
@@ -39,12 +43,14 @@ pub enum MediaFileOriginProductCategory {
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
impl_enum_display_and_debug_using_to_str!(MediaFileOriginProductCategory);
impl_mysql_enum_coders!(MediaFileOriginProductCategory);
impl_mysql_from_row!(MediaFileOriginProductCategory);
/// NB: Legacy API for older code.
impl MediaFileOriginProductCategory {
pub fn to_str(&self) -> &'static str {
match self {
Self::FaceAnimator => "face_animator",
Self::TextToSpeech => "tts",
Self::Unknown => "unknown",
Self::VoiceConversion => "voice_conversion",
Self::ZeroShotVoice => "zs_voice",
@@ -54,6 +60,7 @@ impl MediaFileOriginProductCategory {
pub fn from_str(value: &str) -> Result<Self, String> {
match value {
"face_animator" => Ok(Self::FaceAnimator),
"tts" => Ok(Self::TextToSpeech),
"unknown" => Ok(Self::Unknown),
"voice_conversion" => Ok(Self::VoiceConversion),
"zs_voice" => Ok(Self::ZeroShotVoice),
@@ -66,6 +73,7 @@ impl MediaFileOriginProductCategory {
// NB: BTreeSet::from() isn't const, but not worth using LazyStatic, etc.
BTreeSet::from([
Self::FaceAnimator,
Self::TextToSpeech,
Self::Unknown,
Self::VoiceConversion,
Self::ZeroShotVoice,
@@ -84,6 +92,7 @@ mod tests {
#[test]
fn test_serialization() {
assert_serialization(MediaFileOriginProductCategory::FaceAnimator, "face_animator");
assert_serialization(MediaFileOriginProductCategory::TextToSpeech, "tts");
assert_serialization(MediaFileOriginProductCategory::Unknown, "unknown");
assert_serialization(MediaFileOriginProductCategory::VoiceConversion, "voice_conversion");
assert_serialization(MediaFileOriginProductCategory::ZeroShotVoice, "zs_voice");
@@ -92,6 +101,7 @@ mod tests {
#[test]
fn to_str() {
assert_eq!(MediaFileOriginProductCategory::FaceAnimator.to_str(), "face_animator");
assert_eq!(MediaFileOriginProductCategory::TextToSpeech.to_str(), "tts");
assert_eq!(MediaFileOriginProductCategory::Unknown.to_str(), "unknown");
assert_eq!(MediaFileOriginProductCategory::VoiceConversion.to_str(), "voice_conversion");
assert_eq!(MediaFileOriginProductCategory::ZeroShotVoice.to_str(), "zs_voice");
@@ -100,6 +110,7 @@ mod tests {
#[test]
fn from_str() {
assert_eq!(MediaFileOriginProductCategory::from_str("face_animator").unwrap(), MediaFileOriginProductCategory::FaceAnimator);
assert_eq!(MediaFileOriginProductCategory::from_str("tts").unwrap(), MediaFileOriginProductCategory::TextToSpeech);
assert_eq!(MediaFileOriginProductCategory::from_str("unknown").unwrap(), MediaFileOriginProductCategory::Unknown);
assert_eq!(MediaFileOriginProductCategory::from_str("voice_conversion").unwrap(), MediaFileOriginProductCategory::VoiceConversion);
assert_eq!(MediaFileOriginProductCategory::from_str("zs_voice").unwrap(), MediaFileOriginProductCategory::ZeroShotVoice);
@@ -108,8 +119,9 @@ mod tests {
#[test]
fn all_variants() {
let mut variants = MediaFileOriginProductCategory::all_variants();
assert_eq!(variants.len(), 4);
assert_eq!(variants.len(), 5);
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::FaceAnimator));
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::TextToSpeech));
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::Unknown));
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::VoiceConversion));
assert_eq!(variants.pop_first(), Some(MediaFileOriginProductCategory::ZeroShotVoice));
@@ -25,6 +25,7 @@ pub enum MediaFileType {
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
impl_enum_display_and_debug_using_to_str!(MediaFileType);
impl_mysql_enum_coders!(MediaFileType);
impl_mysql_from_row!(MediaFileType);
/// NB: Legacy API for older code.
impl MediaFileType {
@@ -20,6 +20,9 @@ pub enum MediaUploadType {
/// Video files: mp4, etc.
Video,
// Binary files: safetensors ... weights, etc.
Binary,
}
// TODO(bt, 2022-12-21): This desperately needs MySQL integration tests!
@@ -33,6 +36,7 @@ impl MediaUploadType {
Self::Audio => "audio",
Self::Image => "image",
Self::Video => "video",
Self::Binary => "binary",
}
}
@@ -41,6 +45,7 @@ impl MediaUploadType {
"audio" => Ok(Self::Audio),
"image" => Ok(Self::Image),
"video" => Ok(Self::Video),
"binary" => Ok(Self::Binary),
_ => Err(format!("invalid value: {:?}", value)),
}
}
@@ -56,6 +61,7 @@ mod tests {
assert_serialization(MediaUploadType::Audio, "audio");
assert_serialization(MediaUploadType::Image, "image");
assert_serialization(MediaUploadType::Video, "video");
assert_serialization(MediaUploadType::Binary, "binary");
}
#[test]
@@ -63,6 +69,7 @@ mod tests {
assert_eq!(MediaUploadType::Audio.to_str(), "audio");
assert_eq!(MediaUploadType::Image.to_str(), "image");
assert_eq!(MediaUploadType::Video.to_str(), "video");
assert_eq!(MediaUploadType::Binary.to_str(), "binary");
}
#[test]
@@ -70,6 +77,7 @@ mod tests {
assert_eq!(MediaUploadType::from_str("audio").unwrap(), MediaUploadType::Audio);
assert_eq!(MediaUploadType::from_str("image").unwrap(), MediaUploadType::Image);
assert_eq!(MediaUploadType::from_str("video").unwrap(), MediaUploadType::Video);
assert_eq!(MediaUploadType::from_str("binary").unwrap(), MediaUploadType::Binary);
assert!(MediaUploadType::from_str("foo").is_err());
}
}
@@ -1,2 +1,3 @@
pub mod weights_types;
pub mod weights_category;
pub mod weights_file_type;
@@ -5,8 +5,10 @@ use strum::EnumCount;
#[cfg(test)]
use strum::EnumIter;
use utoipa::ToSchema;
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize, ToSchema)]
pub enum WeightsCategory {
#[serde(rename = "image_generation")]
ImageGeneration,
@@ -0,0 +1,68 @@
use std::collections::BTreeSet;
#[cfg(test)]
use strum::EnumCount;
#[cfg(test)]
use strum::EnumIter;
// TODO we will need to scan the checkpoints for malicious code. We can't just trust the file extension.
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
pub enum WeightsFileType {
#[serde(rename = "checkpoint")]
Checkpoint,
#[serde(rename = "safetensors")]
SafeTensors,
}
impl WeightsFileType {
pub fn to_str(&self) -> &'static str {
match self {
Self::Checkpoint => "checkpoint",
Self::SafeTensors => "safetensors",
}
}
pub fn from_str(value: &str) -> Result<Self, String> {
match value {
"checkpoint" => Ok(Self::Checkpoint),
"safetensors" => Ok(Self::SafeTensors),
_ => Err(format!("invalid value: {:?}", value)),
}
}
pub fn all_variants() -> BTreeSet<Self> {
BTreeSet::from([
Self::Checkpoint,
Self::SafeTensors,
])
}
}
impl_enum_display_and_debug_using_to_str!(WeightsFileType);
impl_mysql_enum_coders!(WeightsFileType);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_to_str() {
assert_eq!(WeightsFileType::Checkpoint.to_str(), "checkpoint");
assert_eq!(WeightsFileType::SafeTensors.to_str(), "safetensors");
}
#[test]
fn test_from_str() {
assert_eq!(WeightsFileType::from_str("checkpoint").unwrap(), WeightsFileType::Checkpoint);
assert_eq!(WeightsFileType::from_str("safetensors").unwrap(), WeightsFileType::SafeTensors);
assert!(WeightsFileType::from_str("invalid").is_err());
}
#[test]
fn test_all_variants() {
let variants = WeightsFileType::all_variants();
assert_eq!(variants.len(), 2);
assert!(variants.contains(&WeightsFileType::Checkpoint));
assert!(variants.contains(&WeightsFileType::SafeTensors));
}
}
@@ -4,10 +4,10 @@ use std::collections::BTreeSet;
use strum::EnumCount;
#[cfg(test)]
use strum::EnumIter;
use utoipa::ToSchema;
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize)]
#[derive(Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Deserialize, Serialize,ToSchema)]
pub enum WeightsType {
#[serde(rename = "hifigan_tt2")]
HifiganTacotron2,
@@ -23,6 +23,8 @@ pub enum WeightsType {
Tacotron2,
#[serde(rename = "loRA")]
LoRA,
#[serde(rename = "vall_e")]
VallE,
}
@@ -36,6 +38,7 @@ impl WeightsType {
Self::SoVitsSvc => "so_vits_svc",
Self::Tacotron2 => "tt2",
Self::LoRA => "loRA",
Self::VallE => "vall_e",
}
}
@@ -48,6 +51,7 @@ impl WeightsType {
"so_vits_svc" => Ok(Self::SoVitsSvc),
"tt2" => Ok(Self::Tacotron2),
"loRA" => Ok(Self::LoRA),
"vall_e" => Ok(Self::VallE),
_ => Err(format!("invalid value: {:?}", value)),
}
}
@@ -61,6 +65,7 @@ impl WeightsType {
Self::SoVitsSvc,
Self::Tacotron2,
Self::LoRA,
Self::VallE
])
}
}
@@ -82,6 +87,7 @@ mod tests {
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");
}
#[test]
@@ -93,6 +99,7 @@ mod tests {
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!(WeightsType::from_str("invalid").is_err());
}
@@ -107,5 +114,6 @@ mod tests {
assert!(variants.contains(&WeightsType::SoVitsSvc));
assert!(variants.contains(&WeightsType::Tacotron2));
assert!(variants.contains(&WeightsType::LoRA));
assert!(variants.contains(&WeightsType::VallE));
}
}
@@ -34,6 +34,12 @@ impl_mysql_enum_coders!(JobStatusPlus);
// NB: We can derive `sqlx::Type` instead of using `impl_mysql_enum_coders`
impl Default for JobStatusPlus {
fn default() -> Self {
Self::Pending
}
}
impl JobStatusPlus {
pub fn to_str(&self) -> &'static str {
match self {
@@ -86,6 +92,11 @@ mod tests {
mod explicit_checks {
use super::*;
#[test]
fn test_default() {
assert_eq!(JobStatusPlus::default(), JobStatusPlus::Pending);
}
#[test]
fn test_serialization() {
assert_serialization(JobStatusPlus::Pending, "pending");
+4 -2
View File
@@ -15,9 +15,10 @@ use strum::EnumIter;
/// See also: https://docs.rs/sqlx/0.4.0-beta.1/sqlx/trait.Type.html
///
/// *DO NOT CHANGE VALUES WITHOUT A MIGRATION STRATEGY!*
///
use utoipa::ToSchema;
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, Eq, PartialEq, Deserialize, Serialize, sqlx::Type)]
#[derive(Clone, Copy, Eq, PartialEq, Deserialize, Serialize, sqlx::Type,ToSchema)]
#[serde(rename_all = "lowercase")]
#[sqlx(rename_all = "lowercase")]
pub enum Visibility {
@@ -38,6 +39,7 @@ pub enum Visibility {
impl_enum_display_and_debug_using_to_str!(Visibility);
impl_mysql_from_row!(Visibility);
// For reference, here's what the serde implementation might be if manually written.
// This may be useful for designing composite types in the future:
+1
View File
@@ -32,3 +32,4 @@ mod macros;
pub mod by_table;
pub mod common;
pub mod traits;
@@ -0,0 +1,63 @@
/// Implement `MySqlFromRow` on a type.
macro_rules! impl_mysql_from_row {
($t:ident) => {
// Try to convert a MySQL row and named field into the value type (for non-nullable fields).
impl crate::traits::mysql_from_row::MySqlFromRow<$t> for $t {
fn try_from_mysql_row(row: &sqlx_mysql::MySqlRow, field_name: &str) -> Result<$t, sqlx::Error> {
use sqlx::{Error, Row};
// NB(bt,2023-12-05): For now only string encodings are considered.
// We may want to revisit in the future if we deal with binary data.
let value : String = row.try_get(field_name)?;
let output_type = Self::from_str(&value)
.map_err(|err| {
Error::ColumnDecode {
index: format!("mysql_from_row failure on field {}: {:?}", field_name, err),
source: format!("mysql_from_row failure on field {}: {:?}", field_name, err).into(),
}
})?;
Ok(output_type)
}
// Try to convert a MySQL row and named field into the value type (for nullable fields).
fn try_from_mysql_row_nullable(row: &sqlx_mysql::MySqlRow, field_name: &str) -> Result<Option<$t>, sqlx::Error> {
use sqlx::{Error, Row};
// NB(bt,2023-12-05): For now only string encodings are considered.
// We may want to revisit in the future if we deal with binary data.
// NB(2): Nullable fields decode as Option<T>.
let maybe_value : Option<String> = row.try_get(field_name)?;
let value = match maybe_value {
Some(value) => value,
None => return Ok(None),
};
let output_type = Self::from_str(&value)
.map_err(|err| {
Error::ColumnDecode {
index: format!("mysql_from_row failure on field {}: {:?}", field_name, err),
source: format!("mysql_from_row failure on field {}: {:?}", field_name, err).into(),
}
})?;
Ok(Some(output_type))
}
}
//// TODO: Tests.
//#[cfg(test)]
//#[test]
//fn test_name() {
// use strum::IntoEnumIterator;
// for variant in $t::iter() {
// assert_eq!(format!("{}", variant), variant.to_str());
// }
//}
}
}
+1
View File
@@ -1,3 +1,4 @@
#[macro_use] pub mod impl_enum_display_and_debug_using_to_str;
#[macro_use] pub mod impl_mysql_enum_coders;
#[macro_use] pub mod impl_mysql_from_row;
#[macro_use] pub mod impl_sqlite_enum_coders;
+1
View File
@@ -0,0 +1 @@
pub mod mysql_from_row;
@@ -0,0 +1,11 @@
use sqlx_mysql::MySqlRow;
/// This is a trait (in conjunction with the macro implementation) that makes up for sqlx's
/// QueryBuilder not playing well with FromRow implementation or derivation of named types.
pub trait MySqlFromRow<T> {
/// Attempt to parse a named column from a MySqlRow into the type.
fn try_from_mysql_row(row: &MySqlRow, field_name: &str) -> Result<T, sqlx::Error>;
fn try_from_mysql_row_nullable(row: &MySqlRow, field_name: &str) -> Result<Option<T>, sqlx::Error>;
}
+2 -2
View File
@@ -18,7 +18,7 @@ path = "src/lib.rs"
# (none)
# External
chrono = { version = "=0.4.22", features = ["serde"] }
chrono = { version = "0.4.22", features = ["serde"] }
# Serialization
serde = "1.0.125"
@@ -26,7 +26,7 @@ serde_derive = "1.0.125"
# MySql Database
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
[dev-dependencies]
# Test serialization
+2 -6
View File
@@ -20,15 +20,11 @@ serde_derive = "1.0.125"
# Random
rand = "0.8.5"
# MySql Database
# "offline" supports typesafe builds without a live database
# "rustls" doesn't appear to work with IP addresses, per:
# https://github.com/launchbadge/sqlx/issues/1095#issuecomment-798050828
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
sqlx-core = { version = "0.7.2" }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
utoipa = { version = "4", features = ["actix_extras"] }
utoipa = { version = "4", features = ["chrono","actix_extras"] }
[dev-dependencies]
@@ -8,7 +8,7 @@ use crate::prefixes::TokenPrefix;
/// A rare instance that is *not* a primary key.
/// These are generated as cookies to track users for a better logged out experience and for analytics.
/// We use these as indices into several columns (ML result types, uploads, etc.)
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::Type, Debug, Serialize, Deserialize)]
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::Type, Debug, Serialize, Deserialize, Default)]
#[sqlx(transparent)]
pub struct AnonymousVisitorTrackingToken(pub String);
@@ -7,7 +7,7 @@ use crate::prefixes::TokenPrefix;
use utoipa::ToSchema;
/// The primary key for "generic" inference jobs.
#[derive(Clone, PartialEq, Eq, sqlx::Type, Debug, Serialize, Deserialize,ToSchema)]
#[derive(Clone, PartialEq, Eq, sqlx::Type, Debug, Serialize, Deserialize, Default, ToSchema)]
#[sqlx(transparent)]
pub struct InferenceJobToken(String);
@@ -5,8 +5,9 @@ use serde::Serialize;
use crate::prefixes::TokenPrefix;
use utoipa::ToSchema;
/// The primary key for the "model_weights" table.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::Type, Debug, Serialize, Deserialize)]
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::Type, Debug, Serialize, Deserialize,ToSchema)]
#[sqlx(transparent)]
pub struct ModelWeightToken(pub String);
+2 -2
View File
@@ -4,9 +4,9 @@ use serde::Deserialize;
use serde::Serialize;
use crate::prefixes::TokenPrefix;
use utoipa::ToSchema;
/// The primary key for users.
#[derive(Clone, PartialEq, Eq, sqlx::Type, Debug, Serialize, Deserialize)]
#[derive(Clone, PartialEq, Eq, sqlx::Type, Debug, Serialize, Deserialize, Default,ToSchema)]
#[sqlx(transparent)]
pub struct UserToken(pub String);
@@ -0,0 +1,14 @@
use std::fmt::Debug;
use serde::Deserialize;
use serde::Serialize;
use crate::prefixes::TokenPrefix;
/// The primary key for user weight uploads
#[derive(Clone, PartialEq, Eq, sqlx::Type, Debug, Serialize, Deserialize)]
#[sqlx(transparent)]
pub struct WeightUploadToken(pub String);
impl_string_token!(WeightUploadToken);
impl_crockford_generator!(WeightUploadToken, 32usize, TokenPrefix::WeightUpload, CrockfordLower);
+1 -1
View File
@@ -28,4 +28,4 @@ log = "0.4.14"
# Serialization
serde = "1.0.125"
serde_derive = "1.0.125"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
+1 -1
View File
@@ -28,4 +28,4 @@ log = "0.4.14"
# Serialization
serde = "1.0.125"
serde_derive = "1.0.125"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
+1 -5
View File
@@ -36,14 +36,10 @@ clap = "2.34.0" # TODO: This is really out of date, but has conflicts when updat
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
tokio = { version = "1.6.0", features = ["macros"] }
# MySql Database
# "offline" supports typesafe builds without a live database
# "rustls" doesn't appear to work with IP addresses, per:
# https://github.com/launchbadge/sqlx/issues/1095#issuecomment-798050828
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
elasticsearch = "8.5.0-alpha.1"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
[dev-dependencies]
# None yet
-1
View File
@@ -56,7 +56,6 @@ pub async fn main() -> AnyhowResult<()> {
seed_voice_conversion(&pool).await?;
seed_weights(&pool).await?;
seed_tts_tacotron2(&pool, maybe_bucket_clients.as_ref()).await?;
@@ -1,10 +1,13 @@
use std::vec;
use buckets::public::media_files::original_file;
use log::info;
use tokens::tokens::users::UserToken;
use sqlx::{ MySql, Pool };
use enums::by_table::model_weights::{
weights_types::WeightsType,
weights_category::WeightsCategory,
weights_types::{WeightsType, self},
weights_category::{WeightsCategory, self},
};
use errors::{ anyhow, AnyhowResult };
@@ -15,6 +18,342 @@ use enums::common::visibility::Visibility;
use mysql_queries::queries::users::user::get_user_token_by_username::get_user_token_by_username;
pub async fn seed_weights_for_paging( mysql_pool: &Pool<MySql>,user_token: UserToken) -> AnyhowResult<()> {
let sd1_5_markdown_description = r#"
# Dragonfruit AI Models Update and Workflow
## Last Update for Dragonfruit Models
- **Note:** This might be the last update for a while, focusing next on a realistic merge (WIP).
- **Future Plans:** Possible update to Z.5D v2 after completing the realistic merge.
## New Models Announcement
- **Upcoming Models:** Separate from the Dragonfruit branch, so stay tuned!
## DragonfruitGT v1 (Z v2)
- **Focus:** More fantasy, richer color range, improved landscapes, more masculine men.
- **Alternative:** Use DragonfruitOG v2.0 for thicker lines, simpler colors.
## GT v1.0 (Z v2.0) Features
- Improved backgrounds
- Deeper contrast and color
- Bolder lines and more detail
### Settings for GT
- Steps: 30-50
- Clip skip: 2
- Upscaler: Euler a / DPM++ 2M SDE Karras
- Extensions: Adetailer, VAE: berrysmix
## Workflow for Images
1. **BerryMix VAE:** Ensure correct color.
2. **Adetailer Extension:** Improve faces, bodies, and hands. Also available on Civitai and Huggingface.
3. **Hires Fix:** Denoising strength 0.3 - 0.35, using upscaler "x_NMKD-Superscale-SP_178000_G".
4. **Img2img Transfer:** Use denoising strength 0.3-0.35 with the same prompt and settings. Adjust height after hires.
5. **Second Img2img Transfer:** Low denoising strength 0.1-3.0. Use SD upscale script with "RealESRGAN_x4plus_anime_6B" sampler for size increase.
## DragonfruitZ.5D v1.0
- **Style:** 2.5D model, merging DragonfruitZ with realistic elements.
- **Features:** Skin color variety, masculine men, landscapes, fantasy, NSFW content, detailed faces.
### Settings for Z.5D
- Steps: 30
- Clip skip: 2
- Upscaler: Euler a
- Extensions: Adetailer, VAE: berrysmix
## DragonfruitOG v2.0
- **Refinement:** Of the original Dragonfruit model.
- **Style:** Bold, clean lines and bold colors with decreased detail for simplicity.
- **Improvements:** Teeth, skin color variety, landscapes, fantasy elements, NSFW content.
## DragonfruitZ v1.0
- **Alternative Version:** More fantasy and color range, better landscapes, more masculine men.
- **Suggestion:** Use Dragonfruit (Unisex) v2 for thicker lines and simpler colors.
## Dragonfruit OG v1.0
- **Focus:** More unisex, suitable for creating male characters.
- **Personal Note:** First merge, aimed at 2D, flat, colorful, thick lined styles.
### Personal Settings
- Steps: 20-30
- Clip skip: 2
- Upscaler: Euler a / DPM++ 2M SDE Karras
- Extensions: Adetailer, VAE: berrysmix
*Thank you for your continued support and feedback!*
"#;
let sd1_5_image_token = "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/be706282-2978-42a0-aaa2-73881aad94e9/width=1024/00049-2287632957-1girl,face,curly%20hair,red%20hair,white%20background,.jpeg";
let user_token = user_token;
// create a loop that loops from 1 to 100
for i in 1..=100 {
// create a new weight
let weights_category = match i {
1..=20 => WeightsCategory::VoiceConversion,
21..=40 => WeightsCategory::TextToSpeech,
41..=60 => WeightsCategory::ImageGeneration,
61..=80 => WeightsCategory::ImageGeneration,
81..=100 => WeightsCategory::ImageGeneration,
_ => WeightsCategory::ImageGeneration
};
let weights_types = match i {
1..=20 => WeightsType::RvcV2,
21..=40 => WeightsType::HifiganTacotron2,
41..=60 => WeightsType::StableDiffusion15,
61..=80 => WeightsType::StableDiffusionXL,
81..=100 => WeightsType::LoRA,
_ => WeightsType::LoRA
};
let mut model_weight_token;
let mut title;
let mut description;
let mut description_rendered_html;
let mut original_filename;
let mut original_download_url;
let mut thumbnail_token;
let mut private_bucket_hash:String = "".to_string();
let mut private_bucket_prefix;
let mut private_bucket_extension;
let mut cached_user_ratings_total_count;
let mut cached_user_ratings_positive_count;
let mut cached_user_ratings_negative_count;
let mut cached_user_ratings_ratio;
let mut version;
match i {
1..=20 => {
// voice conversion model
let rvc_markdown_description = r#"
# RVC v2
- **Improved Quality:** The special edition offers significantly better quality with fewer issues. It's the model as initially envisioned.
- Gawr Gura MOS 4
"#;
model_weight_token = ModelWeightToken(format!("{}", i));
title = format!("Gawr Gura: {}", i);
description = format!("Description Number {} The special edition offers significantly better quality with fewer issues. It's the model as initially envisioned.", i);
description_rendered_html = rvc_markdown_description;
original_filename = format!("gura_gwar{}.safetensors", i);
original_download_url = format!("www.google.ca");
// NOTE TO SELF this will be a join for later on for now they can use this.
thumbnail_token = "";
// private_bucket_hash = format!("bucket_hash{}", i);
private_bucket_prefix = format!("_fake");
private_bucket_extension = format!("rvcV2");
cached_user_ratings_total_count = i;
cached_user_ratings_positive_count = i;
cached_user_ratings_negative_count = i;
cached_user_ratings_ratio = i as u32 / 100;
version = i as i32;
println!("Seeding RVCv2 model {}", i);
},
21..=40 =>{
// TTS
model_weight_token = ModelWeightToken(format!("{}", i));
title = format!("dragonfruitGTv1: {}", i);
description = format!("Description Number {}:", i);
let tts_markdown_description = r#"
# HifiGAN Tacotron2
- **Improved Quality:** The special edition offers significantly better quality with fewer issues. It's the model as initially envisioned.
- Goku MOS 4
"#;
description_rendered_html = tts_markdown_description;
original_filename = format!("filename{}.txt", i);
original_download_url = format!("https://civitai.com/api/download/models/149193?type=Model&format=SafeTensor&size=pruned&fp=fp16");
// NOTE TO SELF this will be a join for later on for now they can use this.
thumbnail_token = "";
// private_bucket_hash = format!("bucket_hash{}", i);
private_bucket_prefix = format!("_fake");
private_bucket_extension = format!("tt2");
cached_user_ratings_total_count = i;
cached_user_ratings_positive_count = i;
cached_user_ratings_negative_count = i;
cached_user_ratings_ratio = i as u32 / 100;
version = i as i32;
println!("Seeding TT2 model {}", i);
},
41..=60 => {
// SD 1.5
model_weight_token = ModelWeightToken(format!("{}", i));
title = format!("dragonfruitGTv1: {}", i);
description = format!("Description Number {}:", i);
description_rendered_html = sd1_5_markdown_description;
original_filename = format!("filename{}.txt", i);
original_download_url = format!("https://civitai.com/api/download/models/149193?type=Model&format=SafeTensor&size=pruned&fp=fp16");
// NOTE TO SELF this will be a join for later on for now they can use this.
thumbnail_token = ""; //sd1_5_image_token;
// private_bucket_hash = format!("bucket_hash{}", i);
private_bucket_prefix = format!("_fake");
private_bucket_extension = format!("sd15");
cached_user_ratings_total_count = i;
cached_user_ratings_positive_count = i;
cached_user_ratings_negative_count = i;
cached_user_ratings_ratio = i as u32 / 100;
version = i as i32;
println!("Seeding SD15 model {}", i);
},
61..=80 => {
// SD XL
let sdxl_markdown_description = r#"
# Special Edition Release Announcement
## What's New?
- **Improved Quality:** The special edition offers significantly better quality with fewer issues. It's the model as initially envisioned.
## Tips for Use
1. **Simple Prompts:** Avoid adding words like "detailed, realistic..." or specifying a specific artist. Simply describe what you want to see.
2. **CFG Scale:** A range between 3 and 8.5 is effective (recommended: 7).
3. **Minimum Steps:** Ensure at least 36 steps for optimal results.
4. **Compatibility:**
- Works excellently with Hires fix.
- Great results with unaestheticXLv31 embedding, although sometimes better without it.
5. **Sampling Method:** Either DPM++ 2M SDE Karras or DPM++ 2M Karras.
Have fun! 😊
"#;
let sdxl_image_token = "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/be706282-2978-42a0-aaa2-73881aad94e9/width=1024/00049-2287632957-1girl,face,curly%20hair,red%20hair,white%20background,.jpeg";
model_weight_token = ModelWeightToken(format!("{}", i));
title = format!("SDXL_Niji_Special Edition: {}", i);
description = format!("Description Number {}:", i);
// private_bucket_hash = format!("bucket_hash{}", i);
private_bucket_prefix = format!("_fake");
private_bucket_extension = format!("sdxl");
description_rendered_html = sdxl_markdown_description;
original_filename = format!("filename{}.txt", i);
original_download_url = format!("https://civitai.com/api/download/models/149193?type=Model&format=SafeTensor&size=pruned&fp=fp16");
// NOTE TO SELF this will be a join for later on for now they can use this.
thumbnail_token = "";
cached_user_ratings_total_count = i;
cached_user_ratings_positive_count = i;
cached_user_ratings_negative_count = i;
cached_user_ratings_ratio = i as u32 / 100;
version = i as i32;
println!("Seeding SDXL model {}", i);
},
81..=100 => {
// LoRA
let lora_markdown_description = r#"
- **Improved Quality:** The special edition offers significantly better quality with fewer issues. It's the model as initially envisioned.
- LoRA Gawr Gura
"#;
let lora_image_token = "https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/123645df-dee2-4239-863a-76a150b09c32/width=1024/00000-2171948503.jpeg";
model_weight_token = ModelWeightToken(format!("{}", i));
title = format!("Gawr Gura LoRA: {}", i);
description = format!("Description Number {}:", i);
description_rendered_html = lora_markdown_description;
original_filename = format!("filename{}.txt", i);
original_download_url = format!("https://civitai.com/api/download/models/149193?type=Model&format=SafeTensor&size=pruned&fp=fp16");
// NOTE TO SELF this will be a join for later on for now they can use this.
thumbnail_token = ""; // lora_image_token;
// private_bucket_hash = format!("bucket_hash{}", i);
private_bucket_prefix = format!("_fake");
private_bucket_extension = format!("loRA");
cached_user_ratings_total_count = i;
cached_user_ratings_positive_count = i;
cached_user_ratings_negative_count = i;
cached_user_ratings_ratio = i as u32 / 100;
version = i as i32;
println!("Seeding LoRA model {}", i);
},
_ => {
// We went out of range so have more gwar gura
let rvc_markdown_description = r#"
# RVC v2
- **Improved Quality:** The special edition offers significantly better quality with fewer issues. It's the model as initially envisioned.
- Gawr Gura MOS 4
"#;
model_weight_token = ModelWeightToken(format!("{}", i));
title = format!("Gawr Gura: {}", i);
description = format!("Description Number {} The special edition offers significantly better quality with fewer issues. It's the model as initially envisioned.", i);
description_rendered_html = rvc_markdown_description;
original_filename = format!("gura_gwar{}.safetensors", i);
original_download_url = format!("www.google.ca");
// NOTE TO SELF this will be a join for later on for now they can use this.
thumbnail_token = "";
// private_bucket_hash = format!("bucket_hash{}", i);
private_bucket_prefix = format!("_fake");
private_bucket_extension = format!("rvcV2");
cached_user_ratings_total_count = i;
cached_user_ratings_positive_count = i;
cached_user_ratings_negative_count = i;
cached_user_ratings_ratio = i as u32 / 100;
version = i as i32;
println!("Out of range");
}
}
let args = CreateModelWeightsArgs {
token: &model_weight_token, // replace with actual ModelWeightToken
weights_type: weights_types, // replace with actual WeightsType
weights_category: weights_category, // replace with actual WeightsCategory
title: title,
maybe_thumbnail_token: Some(thumbnail_token.to_string()),
description_markdown: description,
description_rendered_html: description_rendered_html.to_string(),
creator_user_token: Some(&user_token), // replace with actual UserToken
creator_ip_address: "192.168.1.1",
creator_set_visibility: Visibility::Public,
maybe_last_update_user_token: Some(user_token.to_string()),
original_download_url: Some(original_download_url),
original_filename: Some(original_filename),
file_size_bytes: 1024,
file_checksum_sha2: "checksum1".to_string(),
private_bucket_hash: "bucket_hash1".to_string(),
maybe_private_bucket_prefix: Some(private_bucket_prefix),
maybe_private_bucket_extension: Some(private_bucket_extension),
cached_user_ratings_total_count: cached_user_ratings_total_count,
cached_user_ratings_positive_count: cached_user_ratings_positive_count,
cached_user_ratings_negative_count: cached_user_ratings_negative_count,
maybe_cached_user_ratings_ratio: Some(cached_user_ratings_ratio as f32),
version: version,
mysql_pool: &mysql_pool, // replace with actual MySqlPool
};
create_weight(args).await;
}
Ok(())
}
pub async fn seed_weights_for_user_token(
mysql_pool: &Pool<MySql>,
user_token: UserToken
@@ -165,16 +504,9 @@ pub async fn seed_weights_for_user_token(
}
Ok(())
}
pub async fn seed_weights(mysql_pool: &Pool<MySql>) -> AnyhowResult<()> {
info!("Seeding weights...");
let user_token = match get_user_token_by_username(HANASHI_USERNAME, mysql_pool).await? {
None => {
return Err(anyhow!("could not find user hanashi"));
}
Some(token) => token,
};
pub async fn original_seed_weights(mysql_pool: &Pool<MySql>,user_token: UserToken) -> AnyhowResult<()> {
let model_weight_token1 = ModelWeightToken("1".to_string());
let creator_token1 = UserToken("creatorToken1".to_string());
@@ -327,9 +659,22 @@ pub async fn seed_weights(mysql_pool: &Pool<MySql>) -> AnyhowResult<()> {
create_weight(model_weights_arg).await?;
}
// adds this to HANASHI
get_user_token_by_username(HANASHI_USERNAME, mysql_pool).await?;
seed_weights_for_user_token(mysql_pool, user_token).await?;
Ok(())
}
pub async fn seed_weights(mysql_pool: &Pool<MySql>) -> AnyhowResult<()> {
info!("Seeding weights...");
let user_token = match get_user_token_by_username(HANASHI_USERNAME, mysql_pool).await? {
None => {
return Err(anyhow!("could not find user hanashi"));
}
Some(token) => token,
};
//original_seed_weights(mysql_pool,user_token).await?;
//seed_weights_for_user_token(mysql_pool, user_token).await?;
seed_weights_for_paging(mysql_pool,user_token).await?;
Ok(())
}
+1 -5
View File
@@ -33,12 +33,8 @@ log = "0.4.14"
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
tokio = { version = "1.6.0", features = ["macros"] }
# MySql Database
# "offline" supports typesafe builds without a live database
# "rustls" doesn't appear to work with IP addresses, per:
# https://github.com/launchbadge/sqlx/issues/1095#issuecomment-798050828
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
[dev-dependencies]
# None yet
+1 -5
View File
@@ -33,10 +33,6 @@ strum = { version = "0.25", features = ["derive"] }
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
tokio = { version = "1.6.0", features = ["macros"] }
# MySql Database
# "offline" supports typesafe builds without a live database
# "rustls" doesn't appear to work with IP addresses, per:
# https://github.com/launchbadge/sqlx/issues/1095#issuecomment-798050828
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
elasticsearch = "8.5.0-alpha.1"
@@ -45,7 +41,7 @@ elasticsearch = "8.5.0-alpha.1"
serde = "1.0.125"
serde_derive = "1.0.125"
serde_json = "1.0.108"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
[dev-dependencies]
# None yet
+1 -1
View File
@@ -23,7 +23,7 @@ log = "0.4.20"
once_cell = "1.18.0"
rand = "0.8.5"
regex = "1.10.0"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
# Async Runtime
# NB: Not using Tokio here since everything is blocking so far.
+1 -1
View File
@@ -20,7 +20,7 @@ logging = { path = "../../lib/logging" }
# External
clap = { version = "4.4.7", features = ["derive"] }
walkdir = "2.4.0"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
[dev-dependencies]
# None yet
+1 -1
View File
@@ -21,4 +21,4 @@ web_scrapers = { path = "../../lib/web_scrapers" }
# External
clap = "2.34.0" # TODO: This is really out of date, but has conflicts when updated
tokio = { version = "1.6.0", features = ["macros"] }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
+1 -1
View File
@@ -34,7 +34,7 @@ futures-util = "0.3.29"
# Serialization
serde = "1.0.125"
serde_json = "1.0.108"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
[dev-dependencies]
# None yet
@@ -28,4 +28,4 @@ rust-s3 = { version = "0.33.0" }
# Runtime
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
tokio = { version = "1.6.0", features = ["macros"] }
my-workspace-hack = { version = "0.1", path = "../../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
@@ -20,7 +20,7 @@ tokens = { path = "../../../_types/tokens" }
# External
bytes = "1.3"
chrono = { version = "=0.4.22", features = ["serde"] }
chrono = { version = "0.4.22", features = ["serde"] }
log = "0.4.14"
once_cell = "1.9.0"
@@ -22,5 +22,5 @@ subprocess_common = { path = "../../subprocess_common" }
anyhow = "1.0.75"
log = "0.4.14"
tempdir = "0.3.7"
my-workspace-hack = { version = "0.1", path = "../../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
@@ -31,4 +31,4 @@ twitch_oauth2 = "0.6.0-rc.2"
# Serialization
serde = "1.0.125"
serde_derive = "1.0.125"
my-workspace-hack = { version = "0.1", path = "../../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
+1 -1
View File
@@ -23,7 +23,7 @@ server_environment = { path = "../server_environment" }
dotenv = "0.15.0"
hostname = "0.3.1"
log = "0.4.14"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
[dev-dependencies]
# None yet
+1 -1
View File
@@ -22,4 +22,4 @@ anyhow = "1.0.75"
# In-memory caching
lru_time_cache = "0.11.11"
my-workspace-hack = { version = "0.1", path = "../../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
+1 -1
View File
@@ -23,7 +23,7 @@ r2d2_redis = "0.14.0"
serde = "1.0.125"
serde_derive = "1.0.125"
serde_json = "1.0.108"
my-workspace-hack = { version = "0.1", path = "../../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
[dev-dependencies]
# None yet
+1 -1
View File
@@ -16,7 +16,7 @@ path = "src/lib.rs"
[dependencies]
# Internal
errors = { path = "../errors" }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
# External
# KEEP THIS TO A MINIMUM OR NIL.
+1 -1
View File
@@ -14,7 +14,7 @@ path = "src/lib.rs"
[dependencies]
# Internal
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
# External
# (None yet)
+1 -1
View File
@@ -21,7 +21,7 @@ errors = { path = "../errors" }
hmac = "0.10.0" # jwt signing
jwt = "0.13.0"
sha2 = "0.9.5" # hmac signing
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
[dev-dependencies]
# None yet
+1 -1
View File
@@ -16,7 +16,7 @@ path = "src/lib.rs"
[dependencies]
rand = "0.8.5"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
[dev-dependencies]
# None yet
+2 -2
View File
@@ -13,9 +13,9 @@ name = "datetimes"
path = "src/lib.rs"
[dependencies]
chrono = { version = "=0.4.22", features = ["serde"] }
chrono = { version = "0.4.22", features = ["serde"] }
once_cell = "1.18.0"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
[dev-dependencies]
# (none)
+1 -1
View File
@@ -21,4 +21,4 @@ easyenv = { path = "../../../lib/easyenv" }
# External
anyhow = "1.0.75"
url = "2.4.1"
my-workspace-hack = { version = "0.1", path = "../../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
@@ -24,7 +24,7 @@ log = "0.4.14"
# Serialization
serde = { version = "1.0.125", features = ["derive"] }
toml = "0.5.8"
my-workspace-hack = { version = "0.1", path = "../../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
[dev-dependencies]
@@ -21,7 +21,7 @@ reusable_types = { path = "../../../_types/reusable_types" }
# External
anyhow = "1.0.75"
base64 = "0.21.0"
chrono = { version = "=0.4.22", features = ["serde"] }
chrono = { version = "0.4.22", features = ["serde"] }
log = "0.4.14"
once_cell = "1.18.0"
regex = "1.5.4"
@@ -39,7 +39,7 @@ language-tags = "0.3.2" # Parsing single tags richly
serde = "1.0.125"
serde_derive = "1.0.125"
serde_json = "1.0.108"
my-workspace-hack = { version = "0.1", path = "../../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
[dev-dependencies]
actix-rt = "2.9.0"
+1 -1
View File
@@ -20,7 +20,7 @@ storyteller_root = { path = "../storyteller_root" }
dotenv = "0.15.0"
env_logger = "0.10.1"
log = "0.4.14"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
[dev-dependencies]
# None yet
+2 -2
View File
@@ -23,10 +23,10 @@ elasticsearch = "8.5.0-alpha.1"
log = "0.4.14"
# Serialization
chrono = { version = "=0.4.22", features = ["serde"] }
chrono = { version = "0.4.22", features = ["serde"] }
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.108"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
[dev-dependencies]
# None yet
+2 -2
View File
@@ -11,5 +11,5 @@ path = "src/lib.rs"
[dependencies]
errors = { path = "../errors" }
lettre = "0.11.0"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
lettre = "0.11.2"
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
+1 -1
View File
@@ -18,7 +18,7 @@ path = "src/lib.rs"
# Consider the dependencies here wisely.
# Ideally we have almost nothing as a dependency of this crate.
anyhow = "1.0.75"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
[dev-dependencies]
# None yet
+1 -1
View File
@@ -20,7 +20,7 @@ errors = { path = "../../errors" }
# External
zip = "0.6.6"
my-workspace-hack = { version = "0.1", path = "../../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
[dev-dependencies]
# None yet
+1 -1
View File
@@ -16,7 +16,7 @@ path = "src/lib.rs"
[dependencies]
# Internal
errors = { path = "../../errors" }
my-workspace-hack = { version = "0.1", path = "../../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
log = "0.4.20"
# External
+1 -1
View File
@@ -22,7 +22,7 @@ bcrypt = "0.8" # password hashes
data-encoding = "2.3.2"
md-5 = "0.9.1" # DO NOT USE FOR SERIOUS CRYPTO (we use this for gravatar)
ring = "0.17.0-alpha.10"
my-workspace-hack = { version = "0.1", path = "../../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
[dev-dependencies]
# None yet
+1 -1
View File
@@ -21,7 +21,7 @@ errors = { path = "../../errors" }
# External
image = "0.24.1"
my-workspace-hack = { version = "0.1", path = "../../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
[dev-dependencies]
testing = { path = "../../testing" }
+1 -1
View File
@@ -26,7 +26,7 @@ log = "0.4.14"
# Opus is the browser's default (only) recording codec: https://blog.addpipe.com/audio-constraints-getusermedia/
symphonia = { git = "https://github.com/pdeljanov/Symphonia.git", rev = "e1a70095c4409478c1ded499fb8dd4afa540fb90", features = ["aac", "alac", "flac", "isomp4", "mkv", "mp3", "ogg", "opus", "vorbis", "wav"] }
my-workspace-hack = { version = "0.1", path = "../../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
[dev-dependencies]
# None yet
+1 -1
View File
@@ -16,7 +16,7 @@ path = "src/lib.rs"
[dependencies]
# External
infer = "0.11" # Mimetype detection
my-workspace-hack = { version = "0.1", path = "../../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
[dev-dependencies]
# None yet
+1 -1
View File
@@ -19,7 +19,7 @@ path = "src/lib.rs"
# External
uuid = { version = "0.8.2", features = ["v4"] }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
[dev-dependencies]
# None yet
+2 -2
View File
@@ -24,7 +24,7 @@ redis_common = { path = "../redis_common" }
# External
anyhow = "1.0.75"
chrono = { version = "=0.4.22", features = ["serde"] }
chrono = { version = "0.4.22", features = ["serde"] }
log = "0.4.14"
r2d2_redis = "0.14.0"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
+1 -1
View File
@@ -17,7 +17,7 @@ path = "src/lib.rs"
dotenv = "0.15.0"
env_logger = "0.10.1"
log = "0.4.20"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
[dev-dependencies]
# None yet
+2 -6
View File
@@ -25,7 +25,7 @@ tokens = { path = "../../_types/tokens" }
# External
anyhow = "1.0.75"
chrono = { version = "=0.4.22", features = ["serde"] }
chrono = { version = "0.4.22", features = ["serde"] }
dotenv = "0.15.0"
log = "0.4.14"
@@ -39,14 +39,10 @@ serde_derive = "1.0.125"
serde_json = "1.0.108"
toml = "0.5.8"
serial_test = "2.0.0"
# MySql Database
# "offline" supports typesafe builds without a live database
# "rustls" doesn't appear to work with IP addresses, per:
# https://github.com/launchbadge/sqlx/issues/1095#issuecomment-798050828
tokio = { version = "1.6.0", features = ["full"] }
rand = "0.8"
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
@@ -1,4 +1,5 @@
use anyhow::anyhow;
use chrono::{DateTime, Utc};
use log::warn;
use sqlx::{MySql, MySqlPool};
@@ -14,7 +15,7 @@ use tokens::tokens::users::UserToken;
use crate::helpers::boolean_converters::i8_to_bool;
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct GenericInferenceJobStatus {
pub job_token: InferenceJobToken,
@@ -40,7 +41,7 @@ pub struct GenericInferenceJobStatus {
/// Details about the user's original inference request
/// (We may want to present it in the "pending" UI.)
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct RequestDetails {
pub inference_category: InferenceCategory,
pub maybe_model_type: Option<String>, // TODO: Strongly type
@@ -52,7 +53,7 @@ pub struct RequestDetails {
}
/// Details about the generated result
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct ResultDetails {
pub entity_type: String,
pub entity_token: String,
@@ -71,7 +72,7 @@ pub struct ResultDetails {
/// NB: DO NOT EXPOSE TO FRONTEND.
/// This is used to gate access to job termination
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct UserDetails {
pub maybe_creator_user_token: Option<UserToken>,
pub maybe_creator_anonymous_visitor_token: Option<AnonymousVisitorTrackingToken>,
@@ -168,6 +169,11 @@ WHERE jobs.token = ?
}
};
Ok(Some(raw_record_to_public_result(record)))
}
/// Map the internal record type (since we query over several result tables)
fn raw_record_to_public_result(record: RawGenericInferenceJobStatus) -> GenericInferenceJobStatus {
let maybe_model_title = match record.inference_category {
InferenceCategory::LipsyncAnimation => Some("lipsync animation"),
InferenceCategory::TextToSpeech => record.maybe_tts_model_title.as_deref(),
@@ -213,7 +219,7 @@ WHERE jobs.token = ?
})
});
Ok(Some(GenericInferenceJobStatus {
GenericInferenceJobStatus {
job_token: record.job_token,
status: record.status,
attempt_count: record.attempt_count,
@@ -237,9 +243,10 @@ WHERE jobs.token = ?
is_keepalive_required: i8_to_bool(record.is_keepalive_required),
created_at: record.created_at,
updated_at: record.updated_at,
}))
}
}
#[derive(Debug, Default)]
struct RawGenericInferenceJobStatus {
pub job_token: InferenceJobToken,
@@ -281,3 +288,60 @@ struct RawGenericInferenceJobStatus {
pub maybe_successfully_completed_at: Option<DateTime<Utc>>,
}
#[cfg(test)]
mod tests {
use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory;
use crate::queries::generic_inference::web::get_inference_job_status::{raw_record_to_public_result, RawGenericInferenceJobStatus};
#[test]
fn text_to_speech_as_media_file() {
// NB(bt,2023-12-06): After tts migration to media files, these are the results the job system will return
let record = RawGenericInferenceJobStatus {
inference_category: InferenceCategory::TextToSpeech,
maybe_result_entity_type: Some("media_file".to_string()), // NB: Media file record
maybe_result_entity_token: Some("m_00af018cy75pxytpb8wbdx9jqgtp0p".to_string()),
maybe_media_file_public_bucket_directory_hash: Some("3vb91yq71z5zne56saazn4qntt52yter".to_string()), // NB: Media file
maybe_media_file_public_bucket_prefix: Some("fakeyou_".to_string()),
maybe_media_file_public_bucket_extension: Some(".wav".to_string()),
maybe_tts_public_bucket_path: None, // NB: Not a tts_result!
..Default::default()
};
let record = raw_record_to_public_result(record);
assert!(record.maybe_result_details.is_some());
let result_details = record.maybe_result_details.expect("should have result details");
assert_eq!(&result_details.entity_type, "media_file");
assert_eq!(&result_details.entity_token, "m_00af018cy75pxytpb8wbdx9jqgtp0p");
assert_eq!(&result_details.public_bucket_location_or_hash, "3vb91yq71z5zne56saazn4qntt52yter");
assert_eq!(result_details.public_bucket_location_is_hash, true);
}
#[test]
fn text_to_speech_as_tts_result() {
// NB(bt,2023-12-06): Prior to tts migration to media files, these are the results the job system will return
let record = RawGenericInferenceJobStatus {
inference_category: InferenceCategory::TextToSpeech,
maybe_result_entity_type: Some("text_to_speech".to_string()), // NB: TTS record
maybe_result_entity_token: Some("TR:wefhbk4j3yc8d6zwembedbdw4ad4s".to_string()),
maybe_tts_public_bucket_path: Some("/tts_inference_output/c/5/8/vocodes_c58aeaef-84df-4478-9e7f-c64280a852e8.wav".to_string()), // NB: tts_result!
maybe_media_file_public_bucket_directory_hash: None, // NB: Not a media file!
maybe_media_file_public_bucket_prefix: None,
maybe_media_file_public_bucket_extension: None,
..Default::default()
};
let record = raw_record_to_public_result(record);
assert!(record.maybe_result_details.is_some());
let result_details = record.maybe_result_details.expect("should have result details");
assert_eq!(&result_details.entity_type, "text_to_speech");
assert_eq!(&result_details.entity_token, "TR:wefhbk4j3yc8d6zwembedbdw4ad4s");
assert_eq!(&result_details.public_bucket_location_or_hash, "/tts_inference_output/c/5/8/vocodes_c58aeaef-84df-4478-9e7f-c64280a852e8.wav");
assert_eq!(result_details.public_bucket_location_is_hash, false);
}
}
@@ -0,0 +1,166 @@
use anyhow::anyhow;
use sqlx;
use sqlx::MySqlPool;
use enums::by_table::generic_synthetic_ids::id_category::IdCategory;
use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory;
use enums::by_table::media_files::media_file_origin_model_type::MediaFileOriginModelType;
use enums::by_table::media_files::media_file_origin_product_category::MediaFileOriginProductCategory;
use enums::by_table::media_files::media_file_type::MediaFileType;
use errors::AnyhowResult;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::users::UserToken;
use crate::queries::generic_inference::job::list_available_generic_inference_jobs::AvailableInferenceJob;
use crate::queries::generic_synthetic_ids::transactional_increment_generic_synthetic_id::transactional_increment_generic_synthetic_id;
pub struct InsertMediaFileArgs<'a> {
pub pool: &'a MySqlPool,
pub job: &'a AvailableInferenceJob,
pub maybe_mime_type: Option<&'a str>,
pub file_size_bytes: u64,
pub duration_millis: u64,
pub sha256_checksum: &'a str,
// TODO: Media duration.
//pub duration_millis: u64,
pub public_bucket_directory_hash: &'a str,
pub maybe_public_bucket_prefix: Option<&'a str>,
pub maybe_public_bucket_extension: Option<&'a str>,
pub is_on_prem: bool,
pub worker_hostname: &'a str,
pub worker_cluster: &'a str,
}
pub async fn insert_media_file_from_tacotron2(
args: InsertMediaFileArgs<'_>
) -> AnyhowResult<(MediaFileToken, u64)>
{
let result_token = MediaFileToken::generate();
let origin_model_type = MediaFileOriginModelType::Tacotron2;
let mut maybe_creator_file_synthetic_id : Option<u64> = None;
let mut maybe_creator_category_synthetic_id : Option<u64> = None;
let mut transaction = args.pool.begin().await?;
if let Some(creator_user_token) = args.job.maybe_creator_user_token.as_deref() {
let user_token = UserToken::new_from_str(creator_user_token);
let next_media_file_id = transactional_increment_generic_synthetic_id(
&user_token,
IdCategory::MediaFile,
&mut transaction
).await?;
let next_voice_conversion_id = transactional_increment_generic_synthetic_id(
&user_token,
IdCategory::TtsResult,
&mut transaction
).await?;
maybe_creator_file_synthetic_id = Some(next_media_file_id);
maybe_creator_category_synthetic_id = Some(next_voice_conversion_id);
}
let vc_model_token = args.job.maybe_model_token.as_deref();
let creator_ip_address = args.job.creator_ip_address.as_str();
let creator_set_visibility = args.job.creator_set_visibility.clone();
const ORIGIN_CATEGORY : MediaFileOriginCategory = MediaFileOriginCategory::Inference;
const ORIGIN_PRODUCT_CATEGORY : MediaFileOriginProductCategory = MediaFileOriginProductCategory::TextToSpeech;
const MEDIA_TYPE : MediaFileType = MediaFileType::Audio;
let record_id = {
let query_result = sqlx::query!(
r#"
INSERT INTO media_files
SET
token = ?,
origin_category = ?,
origin_product_category = ?,
maybe_origin_model_type = ?,
maybe_origin_model_token = ?,
media_type = ?,
maybe_mime_type = ?,
file_size_bytes = ?,
checksum_sha2 = ?,
public_bucket_directory_hash = ?,
maybe_public_bucket_prefix = ?,
maybe_public_bucket_extension = ?,
maybe_creator_user_token = ?,
maybe_creator_anonymous_visitor_token = ?,
creator_ip_address = ?,
creator_set_visibility = ?,
maybe_creator_file_synthetic_id = ?,
maybe_creator_category_synthetic_id = ?,
is_generated_on_prem = ?,
generated_by_worker = ?,
generated_by_cluster = ?
"#,
result_token.as_str(),
ORIGIN_CATEGORY.to_str(),
ORIGIN_PRODUCT_CATEGORY.to_str(),
origin_model_type.to_str(),
args.job.maybe_model_token,
MEDIA_TYPE.to_str(),
args.maybe_mime_type,
args.file_size_bytes,
args.sha256_checksum,
args.public_bucket_directory_hash,
args.maybe_public_bucket_prefix,
args.maybe_public_bucket_extension,
args.job.maybe_creator_user_token,
args.job.maybe_creator_anonymous_visitor_token,
args.job.creator_ip_address,
args.job.creator_set_visibility.to_str(),
maybe_creator_file_synthetic_id,
maybe_creator_category_synthetic_id,
args.is_on_prem,
args.worker_hostname,
args.worker_cluster
)
.execute(&mut *transaction)
.await;
let record_id = match query_result {
Ok(res) => {
res.last_insert_id()
},
Err(err) => {
// TODO: handle better
//transaction.rollback().await?;
return Err(anyhow!("Mysql error: {:?}", err));
}
};
record_id
};
transaction.commit().await?;
Ok((result_token, record_id))
}
@@ -0,0 +1,262 @@
use chrono::{DateTime, Utc};
use sqlx::{FromRow, MySql, MySqlPool, QueryBuilder, Row};
use sqlx::mysql::MySqlRow;
use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory;
use enums::by_table::media_files::media_file_origin_model_type::MediaFileOriginModelType;
use enums::by_table::media_files::media_file_origin_product_category::MediaFileOriginProductCategory;
use enums::by_table::media_files::media_file_type::MediaFileType;
use enums::common::visibility::Visibility;
use enums::traits::mysql_from_row::MySqlFromRow;
use errors::AnyhowResult;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::users::UserToken;
pub struct MediaFileListPage {
pub records: Vec<MediaFileListItem>,
pub sort_ascending: bool,
/// ID of the first record.
pub first_id: Option<i64>,
/// ID of the last record.
pub last_id: Option<i64>,
}
pub struct MediaFileListItem {
pub token: MediaFileToken,
pub origin_category: MediaFileOriginCategory,
pub origin_product_category: MediaFileOriginProductCategory,
pub maybe_origin_model_type: Option<MediaFileOriginModelType>,
pub maybe_origin_model_token: Option<String>,
pub media_type: MediaFileType,
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
pub maybe_creator_user_token: Option<UserToken>,
pub maybe_creator_username: Option<String>,
pub maybe_creator_display_name: Option<String>,
pub maybe_creator_gravatar_hash: Option<String>,
pub creator_set_visibility: Visibility,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Clone, Copy)]
pub enum ViewAs {
Moderator,
NonModerator,
}
pub struct ListMediaFilesArgs<'a> {
pub limit: usize,
pub maybe_filter_media_type: Option<MediaFileType>,
pub maybe_offset: Option<usize>,
pub cursor_is_reversed: bool,
pub view_as: ViewAs,
pub mysql_pool: &'a MySqlPool,
}
pub async fn list_media_files(args: ListMediaFilesArgs<'_>) -> AnyhowResult<MediaFileListPage> {
let mut query = query_builder(
args.maybe_filter_media_type,
args.limit,
args.maybe_offset,
args.cursor_is_reversed,
args.view_as,
);
let query = query.build_query_as::<MediaFileListItemInternal>();
let results = query.fetch_all(args.mysql_pool).await?;
let first_id = results.first()
.map(|raw_result| raw_result.id);
let last_id = results.last()
.map(|raw_result| raw_result.id);
let results = results.into_iter()
.map(|record| {
MediaFileListItem {
token: record.token,
origin_category: record.origin_category,
origin_product_category: record.origin_product_category,
maybe_origin_model_type: record.maybe_origin_model_type,
maybe_origin_model_token: record.maybe_origin_model_token,
media_type: record.media_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,
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,
creator_set_visibility: record.creator_set_visibility,
created_at: record.created_at,
updated_at: record.updated_at,
}
})
.collect::<Vec<_>>();
Ok(MediaFileListPage {
records: results,
sort_ascending: !args.cursor_is_reversed,
first_id,
last_id,
})
}
fn query_builder<'a>(
maybe_filter_media_type: Option<MediaFileType>,
limit: usize,
maybe_offset: Option<usize>,
cursor_is_reversed: bool,
view_as: ViewAs,
) -> QueryBuilder<'a, MySql> {
// NB: Query cannot be statically checked by sqlx
let mut query_builder: QueryBuilder<MySql> = QueryBuilder::new(
r#"
SELECT
m.id,
m.token,
m.origin_category,
m.origin_product_category,
m.maybe_origin_model_type,
m.maybe_origin_model_token,
m.media_type,
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.maybe_creator_user_token,
u.username as maybe_creator_username,
u.display_name as maybe_creator_display_name,
u.email_gravatar_hash as maybe_creator_gravatar_hash,
m.creator_set_visibility,
m.created_at,
m.updated_at
FROM media_files AS m
LEFT OUTER JOIN users AS u
ON m.maybe_creator_user_token = u.token
WHERE m.user_deleted_at IS NULL
AND m.mod_deleted_at IS NULL
"#
);
if let Some(media_type) = maybe_filter_media_type {
// FIXME: Binding shouldn't require to_str().
// Otherwise, it's calling the Display trait on the raw type which is resulting in an
// incorrect binding and runtime error.
query_builder.push(" AND m.media_file_type = ");
query_builder.push_bind(media_type.to_str());
}
match view_as {
ViewAs::Moderator => {}
ViewAs::NonModerator=> {
// FIXME: Binding shouldn't require to_str().
// Otherwise, it's calling the Display trait on the raw type which is resulting in an
// incorrect binding and runtime error.
query_builder.push(" AND m.creator_set_visibility = ");
query_builder.push_bind(Visibility::Public.to_str());
}
}
if let Some(offset) = maybe_offset {
if cursor_is_reversed {
query_builder.push(format!(" AND m.id < {offset} "));
} else {
query_builder.push(format!(" AND m.id > {offset} "));
}
}
if cursor_is_reversed {
query_builder.push(" ORDER BY m.id ASC ");
} else {
query_builder.push(" ORDER BY m.id DESC ");
}
query_builder.push(format!(" LIMIT {limit} "));
query_builder
}
struct MediaFileListItemInternal {
id: i64,
token: MediaFileToken,
origin_category: MediaFileOriginCategory,
origin_product_category: MediaFileOriginProductCategory,
maybe_origin_model_type: Option<MediaFileOriginModelType>,
maybe_origin_model_token: Option<String>,
media_type: MediaFileType,
public_bucket_directory_hash: String,
maybe_public_bucket_prefix: Option<String>,
maybe_public_bucket_extension: Option<String>,
maybe_creator_user_token: Option<UserToken>,
maybe_creator_username: Option<String>,
maybe_creator_display_name: Option<String>,
maybe_creator_gravatar_hash: Option<String>,
creator_set_visibility: Visibility,
created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
}
// NB(bt,2023-12-05): There's an issue with type hinting in the `as` clauses with QueryBuilder (or
// raw query strings) and sqlx::FromRow, regardless of whether it is derived of manually
// implemented. Perhaps this will improve in the future, but for now manually constructed queries
// cannot have type hints, eg. the following:
//
// m.token as `token: tokens::tokens::media_files::MediaFileToken`,
// m.origin_category as `origin_category: enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory`,
// m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,
//
// This results in the automatic mapping not being able to be found by name (for macro derive), and
// in the manual case `row.try_get()` etc. won't have the correct column name (since the name is the
// full "as" clause).
impl FromRow<'_, MySqlRow> for MediaFileListItemInternal {
fn from_row(row: &MySqlRow) -> Result<Self, sqlx::Error> {
let maybe_creator_user_token : Option<String> = row.try_get("maybe_creator_user_token")?;
let maybe_creator_user_token = maybe_creator_user_token.map(|user_token| UserToken::new(user_token));
Ok(Self {
id: row.try_get("id")?,
token: MediaFileToken::new(row.try_get("token")?),
origin_category: MediaFileOriginCategory::try_from_mysql_row(row, "origin_category")?,
origin_product_category: MediaFileOriginProductCategory::try_from_mysql_row(row, "origin_product_category")?,
maybe_origin_model_type: MediaFileOriginModelType::try_from_mysql_row_nullable(row, "maybe_origin_model_type")?,
maybe_origin_model_token: row.try_get("maybe_origin_model_token")?,
media_type: MediaFileType::try_from_mysql_row(row, "media_type")?,
public_bucket_directory_hash: row.try_get("public_bucket_directory_hash")?,
maybe_public_bucket_prefix: row.try_get("maybe_public_bucket_prefix")?,
maybe_public_bucket_extension: row.try_get("maybe_public_bucket_extension")?,
maybe_creator_user_token,
maybe_creator_username: row.try_get("maybe_creator_username")?,
maybe_creator_display_name: row.try_get("maybe_creator_display_name")?,
maybe_creator_gravatar_hash: row.try_get("maybe_creator_gravatar_hash")?,
creator_set_visibility: Visibility::try_from_mysql_row(row, "creator_set_visibility")?,
created_at: row.try_get("created_at")?,
updated_at: row.try_get("updated_at")?,
})
}
}
@@ -0,0 +1,243 @@
use chrono::{DateTime, Utc};
use sqlx::{FromRow, MySql, MySqlPool, QueryBuilder, Row};
use sqlx::mysql::MySqlRow;
use enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory;
use enums::by_table::media_files::media_file_origin_model_type::MediaFileOriginModelType;
use enums::by_table::media_files::media_file_origin_product_category::MediaFileOriginProductCategory;
use enums::by_table::media_files::media_file_type::MediaFileType;
use enums::common::visibility::Visibility;
use enums::traits::mysql_from_row::MySqlFromRow;
use errors::AnyhowResult;
use tokens::tokens::media_files::MediaFileToken;
pub struct MediaFileListPage {
pub records: Vec<MediaFileListItem>,
pub sort_ascending: bool,
/// ID of the first record.
pub first_id: Option<i64>,
/// ID of the last record.
pub last_id: Option<i64>,
}
pub struct MediaFileListItem {
pub token: MediaFileToken,
pub origin_category: MediaFileOriginCategory,
pub origin_product_category: MediaFileOriginProductCategory,
pub maybe_origin_model_type: Option<MediaFileOriginModelType>,
pub maybe_origin_model_token: Option<String>,
pub media_type: MediaFileType,
pub public_bucket_directory_hash: String,
pub maybe_public_bucket_prefix: Option<String>,
pub maybe_public_bucket_extension: Option<String>,
pub creator_set_visibility: Visibility,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Clone, Copy)]
pub enum ViewAs {
Author,
Moderator,
AnotherUser,
}
pub struct ListMediaFileForUserArgs<'a> {
pub username: &'a str,
pub limit: usize,
pub maybe_filter_media_type: Option<MediaFileType>,
pub maybe_offset: Option<usize>,
pub cursor_is_reversed: bool,
pub view_as: ViewAs,
pub mysql_pool: &'a MySqlPool,
}
pub async fn list_media_files_for_user(args: ListMediaFileForUserArgs<'_>) -> AnyhowResult<MediaFileListPage> {
let mut query = query_builder(
args.maybe_filter_media_type,
args.username,
args.limit,
args.maybe_offset,
args.cursor_is_reversed,
args.view_as,
);
let query = query.build_query_as::<MediaFileListItemInternal>();
let results = query.fetch_all(args.mysql_pool).await?;
let first_id = results.first()
.map(|raw_result| raw_result.id);
let last_id = results.last()
.map(|raw_result| raw_result.id);
let results = results.into_iter()
.map(|record| {
MediaFileListItem {
token: record.token,
origin_category: record.origin_category,
origin_product_category: record.origin_product_category,
maybe_origin_model_type: record.maybe_origin_model_type,
maybe_origin_model_token: record.maybe_origin_model_token,
media_type: record.media_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,
created_at: record.created_at,
updated_at: record.updated_at,
}
})
.collect::<Vec<_>>();
Ok(MediaFileListPage {
records: results,
sort_ascending: !args.cursor_is_reversed,
first_id,
last_id,
})
}
fn query_builder<'a>(
maybe_filter_media_type: Option<MediaFileType>,
username: &'a str,
limit: usize,
maybe_offset: Option<usize>,
cursor_is_reversed: bool,
view_as: ViewAs,
) -> QueryBuilder<'a, MySql> {
// NB: Query cannot be statically checked by sqlx
let mut query_builder: QueryBuilder<MySql> = QueryBuilder::new(
r#"
SELECT
m.id,
m.token,
m.origin_category,
m.origin_product_category,
m.maybe_origin_model_type,
m.maybe_origin_model_token,
m.media_type,
m.public_bucket_directory_hash,
m.maybe_public_bucket_prefix,
m.maybe_public_bucket_extension,
m.creator_set_visibility,
m.created_at,
m.updated_at
FROM media_files AS m
LEFT OUTER JOIN users AS u
ON m.maybe_creator_user_token = u.token
WHERE m.user_deleted_at IS NULL
AND m.mod_deleted_at IS NULL
"#
);
query_builder.push(" AND u.username = ");
query_builder.push_bind(username);
if let Some(media_type) = maybe_filter_media_type {
// FIXME: Binding shouldn't require to_str().
// Otherwise, it's calling the Display trait on the raw type which is resulting in an
// incorrect binding and runtime error.
query_builder.push(" AND m.media_file_type = ");
query_builder.push_bind(media_type.to_str());
}
match view_as {
ViewAs::Author => {}
ViewAs::Moderator => {}
ViewAs::AnotherUser => {
// FIXME: Binding shouldn't require to_str().
// Otherwise, it's calling the Display trait on the raw type which is resulting in an
// incorrect binding and runtime error.
query_builder.push(" AND m.creator_set_visibility = ");
query_builder.push_bind(Visibility::Public.to_str());
}
}
if let Some(offset) = maybe_offset {
if cursor_is_reversed {
query_builder.push(format!(" AND m.id < {offset} "));
} else {
query_builder.push(format!(" AND m.id > {offset} "));
}
}
if cursor_is_reversed {
query_builder.push(" ORDER BY m.id ASC ");
} else {
query_builder.push(" ORDER BY m.id DESC ");
}
query_builder.push(format!(" LIMIT {limit} "));
query_builder
}
struct MediaFileListItemInternal {
id: i64,
token: MediaFileToken,
origin_category: MediaFileOriginCategory,
origin_product_category: MediaFileOriginProductCategory,
maybe_origin_model_type: Option<MediaFileOriginModelType>,
maybe_origin_model_token: Option<String>,
media_type: MediaFileType,
public_bucket_directory_hash: String,
maybe_public_bucket_prefix: Option<String>,
maybe_public_bucket_extension: Option<String>,
creator_set_visibility: Visibility,
created_at: DateTime<Utc>,
updated_at: DateTime<Utc>,
}
// NB(bt,2023-12-05): There's an issue with type hinting in the `as` clauses with QueryBuilder (or
// raw query strings) and sqlx::FromRow, regardless of whether it is derived of manually
// implemented. Perhaps this will improve in the future, but for now manually constructed queries
// cannot have type hints, eg. the following:
//
// m.token as `token: tokens::tokens::media_files::MediaFileToken`,
// m.origin_category as `origin_category: enums::by_table::media_files::media_file_origin_category::MediaFileOriginCategory`,
// m.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,
//
// This results in the automatic mapping not being able to be found by name (for macro derive), and
// in the manual case `row.try_get()` etc. won't have the correct column name (since the name is the
// full "as" clause).
impl FromRow<'_, MySqlRow> for MediaFileListItemInternal {
fn from_row(row: &MySqlRow) -> Result<Self, sqlx::Error> {
Ok(Self {
id: row.try_get("id")?,
token: MediaFileToken::new(row.try_get("token")?),
origin_category: MediaFileOriginCategory::try_from_mysql_row(row, "origin_category")?,
origin_product_category: MediaFileOriginProductCategory::try_from_mysql_row(row, "origin_product_category")?,
maybe_origin_model_type: MediaFileOriginModelType::try_from_mysql_row_nullable(row, "maybe_origin_model_type")?,
maybe_origin_model_token: row.try_get("maybe_origin_model_token")?,
media_type: MediaFileType::try_from_mysql_row(row, "media_type")?,
public_bucket_directory_hash: row.try_get("public_bucket_directory_hash")?,
maybe_public_bucket_prefix: row.try_get("maybe_public_bucket_prefix")?,
maybe_public_bucket_extension: row.try_get("maybe_public_bucket_extension")?,
creator_set_visibility: Visibility::try_from_mysql_row(row, "creator_set_visibility")?,
created_at: row.try_get("created_at")?,
updated_at: row.try_get("updated_at")?,
})
}
}
@@ -1,7 +1,10 @@
pub mod get_media_file;
pub mod insert_media_file_from_face_animation;
pub mod insert_media_file_from_zero_shot_tts;
pub mod insert_media_file_from_cli_tool;
pub mod insert_media_file_from_voice_conversion;
pub mod delete_media_file;
pub mod get_media_file;
pub mod insert_media_file_from_cli_tool;
pub mod insert_media_file_from_face_animation;
pub mod insert_media_file_from_tacotron2;
pub mod insert_media_file_from_voice_conversion;
pub mod insert_media_file_from_zero_shot_tts;
pub mod list_media_files;
pub mod list_media_files_for_user;
pub mod update_media_file;
@@ -7,6 +7,7 @@ use enums::by_table::model_weights::{
use log::warn;
use enums::common::visibility::Visibility;
use tokens::tokens::{ users::UserToken, model_weights::ModelWeightToken };
pub struct CreateModelWeightsArgs<'a> {
pub token: &'a ModelWeightToken,
pub weights_type: WeightsType,
@@ -71,11 +71,7 @@ pub async fn list_weights_by_creator_username(
let raw_weights: Vec<RawWeightJoinUser> = get_raw_weights_by_creator_username(&mut connection, creator_username, can_see_deleted).await?;
let weights_records: Vec<WeightsJoinUserRecord> = map_to_weights(raw_weights).await;
let filtered_weights: Vec<WeightsJoinUserRecord> = weights_records.into_iter().filter(|weight| {
weight.creator_username == creator_username && weight.creator_set_visibility == Visibility::Public && can_see_deleted
}).collect();
Ok(filtered_weights)
Ok(weights_records)
}
async fn get_raw_weights_by_creator_username(
@@ -133,13 +133,13 @@ impl ListWeightsQueryBuilder {
self
}
pub fn weights_type(mut self, weights_type: WeightsType) -> Self {
self.weights_type = Some(weights_type);
pub fn weights_type(mut self, weights_type: Option<WeightsType>) -> Self {
self.weights_type = weights_type;
self
}
pub fn weights_category(mut self, weights_category: WeightsCategory) -> Self {
self.weights_category = Some(weights_category);
pub fn weights_category(mut self, weights_category: Option<WeightsCategory>) -> Self {
self.weights_category = weights_category;
self
}
@@ -505,7 +505,7 @@ mod tests {
#[test]
fn predicates_scoped_to_weights_type() {
let query_builder = ListWeightsQueryBuilder::new().weights_type(WeightsType::RvcV2);
let query_builder = ListWeightsQueryBuilder::new().weights_type(Some(WeightsType::RvcV2));
assert_eq!(
&query_builder.build_predicates(),
" WHERE model_weights.weights_type = ? \
@@ -520,7 +520,7 @@ mod tests {
#[test]
fn predicates_scoped_to_weights_category() {
let query_builder = ListWeightsQueryBuilder::new().weights_category(
WeightsCategory::VoiceConversion
Some(WeightsCategory::VoiceConversion)
);
println!("Query HERE! {}", &query_builder.build_predicates());
assert_eq!(
@@ -3,8 +3,9 @@ mod tests {
use std::result;
use anyhow::Ok;
use enums::by_table::model_weights::{weights_types, weights_category};
use sqlx::{MySqlPool, Acquire};
use chrono::offset;
use enums::by_table::model_weights::{ weights_types, weights_category };
use sqlx::{ MySqlPool, Acquire };
use rand::Rng;
use tokio;
@@ -24,7 +25,12 @@ mod tests {
use crate::queries::model_weights::create_weight::create_weight;
use crate::queries::model_weights::get_weight::get_weight_by_token;
use crate::queries::model_weights::delete_weights::{ delete_weights_as_user, delete_weights_as_mod, undelete_weights_as_mod , undelete_weights_as_user};
use crate::queries::model_weights::delete_weights::{
delete_weights_as_user,
delete_weights_as_mod,
undelete_weights_as_mod,
undelete_weights_as_user,
};
use crate::queries::model_weights::list_weights_by_user::list_weights_by_creator_username;
use crate::queries::model_weights::list_weights_query_builder::ListWeightsQueryBuilder;
@@ -45,23 +51,19 @@ mod tests {
pub async fn delete_all_weights_for_table(mysql_pool: &MySqlPool) -> AnyhowResult<()> {
// write a query that deletes all weights
let _r = sqlx::query("DELETE FROM model_weights")
.execute(mysql_pool)
.await?;
let _r = sqlx::query("DELETE FROM model_weights").execute(mysql_pool).await?;
Ok(())
}
#[tokio::test]
#[serial]
async fn test_create_weights() -> AnyhowResult<()> {
let pool = setup().await;
// create a random token for the model weight
let mut rng = rand::thread_rng();
let random_number: u32 = rng.gen();
let model_weight_token1 = ModelWeightToken(random_number.to_string());
let creator_token1 = UserToken("creatorToken!1".to_string());
let args = CreateModelWeightsArgs {
@@ -131,7 +133,6 @@ mod tests {
#[tokio::test]
#[serial]
async fn test_delete_and_undelete_weights_user() -> AnyhowResult<()> {
let pool = setup().await;
// create a random token for the model weight
let mut rng = rand::thread_rng();
@@ -170,7 +171,7 @@ mod tests {
create_weight(args).await?;
delete_weights_as_user(&model_weight_token1, &pool).await?;
let result = get_weight_by_token(&model_weight_token1, true, &pool).await?;
let result = result.unwrap();
@@ -261,7 +262,6 @@ mod tests {
}
}
undelete_weights_as_mod(&model_weight_token1, &pool).await?;
let result = get_weight_by_token(&model_weight_token1, true, &pool).await?;
let result = result.unwrap();
@@ -285,35 +285,54 @@ mod tests {
#[serial]
async fn list_all_weights_by_user() -> AnyhowResult<()> {
let pool = setup().await;
let creator_username = "hanashi".to_string();
let can_see_deleted = true;
seed_weights_with_category_and_type(WeightsType::LoRA,WeightsCategory::ImageGeneration,5,&creator_username).await?;
seed_weights_with_category_and_type(
WeightsType::LoRA,
WeightsCategory::ImageGeneration,
5,
&creator_username
).await?;
let weights_by_username = list_weights_by_creator_username(&pool, &creator_username, can_see_deleted).await?;
let weights_by_username = list_weights_by_creator_username(
&pool,
&creator_username,
can_see_deleted
).await?;
for weight in weights_by_username.iter() {
// print weight
println!("weight: {:?}", weight.creator_username);
}
assert_eq!(weights_by_username.len(), 5);
Ok(())
}
async fn seed_weights_with_category_and_type(weights_type:WeightsType,weights_category:WeightsCategory,number_of_items:u32,creator_username:&str) -> AnyhowResult<()> {
async fn seed_weights_with_category_and_type(
weights_type: WeightsType,
weights_category: WeightsCategory,
number_of_items: u32,
creator_username: &str
) -> AnyhowResult<()> {
let pool = setup().await;
let creator_token = get_user_token_by_username(&creator_username, &pool).await?.unwrap_or_else(
|| panic!("Could not find user with username {}", creator_username)
let creator_token = get_user_token_by_username(
&creator_username,
&pool
).await?.unwrap_or_else(||
panic!("Could not find user with username {}", creator_username)
);
for i in 0..number_of_items {
let mut rng = rand::thread_rng();
let number: u32 = rng.gen();
let model_weight_token = ModelWeightToken(number.to_string());
let args = CreateModelWeightsArgs {
token: &model_weight_token, // replace with actual ModelWeightToken
weights_type:weights_type, // replace with actual WeightsType
weights_type: weights_type, // replace with actual WeightsType
weights_category: weights_category, // replace with actual WeightsCategory
title: format!("Title {}", i),
maybe_thumbnail_token: Some(format!("Thumbnail {}", i)),
@@ -342,7 +361,6 @@ mod tests {
}
Ok(())
}
// Tests paging for the list weights query builder
@@ -352,40 +370,45 @@ mod tests {
let pool = setup().await;
let creator_username = "hanashi".to_string();
seed_weights_with_category_and_type(WeightsType::LoRA,WeightsCategory::ImageGeneration,20,&creator_username).await?;
seed_weights_with_category_and_type(
WeightsType::LoRA,
WeightsCategory::ImageGeneration,
20,
&creator_username
).await?;
let qb = ListWeightsQueryBuilder::new()
.offset(Some(0))
.weights_type(WeightsType::LoRA)
.weights_category(WeightsCategory::ImageGeneration)
.limit(10)
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false);
.offset(Some(0))
.weights_type(Some(WeightsType::LoRA))
.weights_category(Some(WeightsCategory::ImageGeneration))
.limit(10)
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false);
let result = qb.perform_query_for_page(&pool).await?;
// let result = qb.perform_query_for_page(&pool).await?;
assert_eq!(result.weights.len(), 10);
let qb = ListWeightsQueryBuilder::new()
.weights_type(WeightsType::LoRA)
.weights_category(WeightsCategory::ImageGeneration)
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false)
.offset(Some(10))
.limit(10);
.weights_type(Some(WeightsType::LoRA))
.weights_category(Some(WeightsCategory::ImageGeneration))
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false)
.offset(Some(10))
.limit(10);
let result = qb.perform_query_for_page(&pool).await?;
assert_eq!(result.weights.len(), 10);
let qb = ListWeightsQueryBuilder::new()
.weights_type(WeightsType::LoRA)
.weights_category(WeightsCategory::ImageGeneration)
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false)
.offset(Some(10))
.limit(10);
.weights_type(Some(WeightsType::LoRA))
.weights_category(Some(WeightsCategory::ImageGeneration))
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false)
.offset(Some(10))
.limit(10);
let result = qb.perform_query_for_page(&pool).await?;
@@ -394,27 +417,31 @@ mod tests {
Ok(())
}
#[tokio::test]
#[serial]
async fn list_weights_query_build_test_asc_desc_cursor_reverse() -> AnyhowResult<()> {
let pool = setup().await;
let creator_username = "hanashi".to_string();
seed_weights_with_category_and_type(WeightsType::LoRA,WeightsCategory::ImageGeneration,10,&creator_username).await?;
seed_weights_with_category_and_type(
WeightsType::LoRA,
WeightsCategory::ImageGeneration,
10,
&creator_username
).await?;
let qb = ListWeightsQueryBuilder::new()
.offset(Some(0))
.weights_type(WeightsType::LoRA)
.weights_category(WeightsCategory::ImageGeneration)
.limit(10)
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false)
.sort_ascending(true)
.cursor_is_reversed(false);
.offset(Some(0))
.weights_type(Some(WeightsType::LoRA))
.weights_category(Some(WeightsCategory::ImageGeneration))
.limit(10)
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false)
.sort_ascending(true)
.cursor_is_reversed(false);
let result = qb.perform_query_for_page(&pool).await?;
// write code that will loop through the result and check if the ids are in ascending order
let mut previous_id = 0;
for weight in result.weights.iter() {
@@ -424,19 +451,18 @@ mod tests {
}
let qb = ListWeightsQueryBuilder::new()
.offset(Some(0))
.weights_type(WeightsType::LoRA)
.weights_category(WeightsCategory::ImageGeneration)
.limit(10)
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false)
.sort_ascending(false)
.cursor_is_reversed(true);
.offset(Some(0))
.weights_type(Some(WeightsType::LoRA))
.weights_category(Some(WeightsCategory::ImageGeneration))
.limit(10)
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false)
.sort_ascending(false)
.cursor_is_reversed(true);
let result = qb.perform_query_for_page(&pool).await?;
// write code that will loop through the result and check if the ids are in descending order
let mut previous_id
= 10000;
let mut previous_id = 10000;
for weight in result.weights.iter() {
let current_id = weight.weight_id;
assert!(current_id < previous_id);
@@ -452,18 +478,23 @@ mod tests {
let pool = setup().await;
let creator_username = "hanashi".to_string();
seed_weights_with_category_and_type(WeightsType::LoRA,WeightsCategory::ImageGeneration,10,&creator_username).await?;
seed_weights_with_category_and_type(
WeightsType::LoRA,
WeightsCategory::ImageGeneration,
10,
&creator_username
).await?;
let qb = ListWeightsQueryBuilder::new()
.offset(Some(0))
.weights_type(WeightsType::LoRA)
.weights_category(WeightsCategory::ImageGeneration)
.limit(10)
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false)
.sort_ascending(true);
.offset(Some(0))
.weights_type(Some(WeightsType::LoRA))
.weights_category(Some(WeightsCategory::ImageGeneration))
.limit(10)
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false)
.sort_ascending(true);
let result = qb.perform_query_for_page(&pool).await?;
// write code that will loop through the result and check if the ids are in ascending order
let mut previous_id = 0;
for weight in result.weights.iter() {
@@ -473,19 +504,18 @@ mod tests {
}
let qb = ListWeightsQueryBuilder::new()
.offset(Some(0))
.weights_type(WeightsType::LoRA)
.weights_category(WeightsCategory::ImageGeneration)
.limit(10)
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false)
.sort_ascending(false);
.offset(Some(0))
.weights_type(Some(WeightsType::LoRA))
.weights_category(Some(WeightsCategory::ImageGeneration))
.limit(10)
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false)
.sort_ascending(false);
let result = qb.perform_query_for_page(&pool).await?;
// write code that will loop through the result and check if the ids are in descending order
let mut previous_id
= 10000;
let mut previous_id = 10000;
for weight in result.weights.iter() {
let current_id = weight.weight_id;
assert!(current_id < previous_id);
@@ -499,11 +529,14 @@ mod tests {
#[serial]
async fn list_weights_query_build_test() -> AnyhowResult<()> {
let pool = setup().await;
let creator_username = "hanashi".to_string();
let creator_token = get_user_token_by_username(&creator_username, &pool).await?.unwrap_or_else(
|| panic!("Could not find user with username {}", creator_username)
let creator_token = get_user_token_by_username(
&creator_username,
&pool
).await?.unwrap_or_else(||
panic!("Could not find user with username {}", creator_username)
);
for i in 0..6 {
@@ -515,7 +548,7 @@ mod tests {
WeightsType::RvcV2
} else if i % 5 == 3 {
WeightsType::StableDiffusionXL
} else if i ^ 5 == 4 {
} else if (i ^ 5) == 4 {
WeightsType::SoVitsSvc
} else {
WeightsType::StableDiffusion15
@@ -529,7 +562,7 @@ mod tests {
WeightsCategory::VoiceConversion
} else if i % 5 == 3 {
WeightsCategory::ImageGeneration
} else if i ^ 5 == 4 {
} else if (i ^ 5) == 4 {
WeightsCategory::VoiceConversion
} else {
WeightsCategory::ImageGeneration
@@ -539,10 +572,10 @@ mod tests {
let number: u32 = rng.gen();
let model_weight_token = ModelWeightToken(number.to_string());
let args = CreateModelWeightsArgs {
token: &model_weight_token, // replace with actual ModelWeightToken
weights_type:weights_type, // replace with actual WeightsType
weights_type: weights_type, // replace with actual WeightsType
weights_category: weights_category, // replace with actual WeightsCategory
title: format!("Title {}", i),
maybe_thumbnail_token: Some(format!("Thumbnail {}", i)),
@@ -572,28 +605,192 @@ mod tests {
// try a query with all the filters
let qb = ListWeightsQueryBuilder::new()
.weights_type(WeightsType::Tacotron2)
.weights_category(WeightsCategory::TextToSpeech)
.weights_type(Some(WeightsType::Tacotron2))
.weights_category(Some(WeightsCategory::TextToSpeech))
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false);
let result = qb.perform_query_for_page(&pool).await?;
assert_eq!(result.weights.len(), 2);
let qb = ListWeightsQueryBuilder::new()
.weights_type(WeightsType::LoRA)
.weights_category(WeightsCategory::ImageGeneration)
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false);
.weights_type(Some(WeightsType::LoRA))
.weights_category(Some(WeightsCategory::ImageGeneration))
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false);
let result = qb.perform_query_for_page(&pool).await?;
assert_eq!(result.weights.len(), 1);
assert_eq!(result.weights[0].weights_category, WeightsCategory::ImageGeneration);
assert_eq!(result.weights[0].weights_type, WeightsType::LoRA);
Ok(())
}
}
#[tokio::test]
#[serial]
async fn list_weights_query_builder_page_type_and_category() -> AnyhowResult<()> {
let db_connection_string = DEFAULT_MYSQL_CONNECTION_STRING;
let pool = MySqlPoolOptions::new()
.max_connections(3)
.connect(&db_connection_string).await
.unwrap();
// using the seeded weights from the previous test
let creator_username = "hanashi".to_string();
for i in 0..4 {
let weights_types = vec![
WeightsType::HifiganTacotron2,
WeightsType::LoRA,
WeightsType::RvcV2,
WeightsType::StableDiffusionXL
];
let weights_type = weights_types[i as usize];
let qb = ListWeightsQueryBuilder::new()
.weights_type(Some(weights_type))
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false);
println!("weights_type: {:?}", weights_type);
let result = qb.perform_query_for_page(&pool).await?;
assert_eq!(result.weights.len(), 20, "Should be 20 weights");
assert_eq!(
result.weights[0].weights_type,
weights_type,
"Should be the same weights type"
);
}
Ok(())
}
#[tokio::test]
#[serial]
async fn list_weight_query_builder_all() -> AnyhowResult<()> {
let db_connection_string = DEFAULT_MYSQL_CONNECTION_STRING;
let pool = MySqlPoolOptions::new()
.max_connections(3)
.connect(&db_connection_string).await
.unwrap();
let qb = ListWeightsQueryBuilder::new()
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false)
.limit(11);
let result = qb.perform_query_for_page(&pool).await?;
assert_eq!(result.weights.len(), 11);
Ok(())
}
#[tokio::test]
#[serial]
async fn list_limits() -> AnyhowResult<()> {
let db_connection_string = DEFAULT_MYSQL_CONNECTION_STRING;
let pool = MySqlPoolOptions::new()
.max_connections(3)
.connect(&db_connection_string).await
.unwrap();
let qb = ListWeightsQueryBuilder::new()
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false)
.weights_category(Some(WeightsCategory::ImageGeneration))
.limit(10);
let result = qb.perform_query_for_page(&pool).await?;
assert_eq!(result.weights.len(), 10);
assert_eq!(result.weights[0].weights_category, WeightsCategory::ImageGeneration);
let qb = ListWeightsQueryBuilder::new()
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false);
let result = qb.perform_query_for_page(&pool).await?;
// by default 25
assert_eq!(result.weights.len(), 25);
assert_eq!(result.weights[0].weights_category, WeightsCategory::ImageGeneration);
// over query limit
let qb: ListWeightsQueryBuilder = ListWeightsQueryBuilder::new()
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false)
.weights_category(Some(WeightsCategory::ImageGeneration))
.limit(80);
let result = qb.perform_query_for_page(&pool).await?;
assert_eq!(result.weights.len(), 60);
// pick a random value in here
assert_eq!(result.weights[9].weights_category, WeightsCategory::ImageGeneration);
Ok(())
}
#[tokio::test]
#[serial]
async fn page_across_a_category() -> AnyhowResult<()> {
// paging across a category and it works
let db_connection_string = DEFAULT_MYSQL_CONNECTION_STRING;
// how we implement paging.
let page:u16 = 0;
let page_size: u16 = 20;
let pool = MySqlPoolOptions::new()
.max_connections(3)
.connect(&db_connection_string).await
.unwrap();
let qb = ListWeightsQueryBuilder::new()
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false)
.weights_category(Some(WeightsCategory::ImageGeneration))
.sort_ascending(true)
.limit(page_size);
let result = qb.perform_query_for_page(&pool).await?;
assert_eq!(result.weights.len(), page_size as usize);
assert_eq!(result.weights[0].weights_category, WeightsCategory::ImageGeneration);
let page = 1;
let offset = page * page_size;
assert_eq!(result.weights.len(), page_size as usize);
assert_eq!(result.weights[0].weights_category, WeightsCategory::ImageGeneration);
let qb = ListWeightsQueryBuilder::new()
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false)
.weights_category(Some(WeightsCategory::ImageGeneration))
.limit(page_size)
.sort_ascending(true)
.offset(Some(offset.into()));
let result = qb.perform_query_for_page(&pool).await?;
let page = 2;
let offset = page * page_size;
assert_eq!(result.weights.len(), page_size as usize);
assert_eq!(result.weights[0].weights_category, WeightsCategory::ImageGeneration);
let qb = ListWeightsQueryBuilder::new()
.scope_creator_username(Some("hanashi"))
.include_user_deleted_results(false)
.sort_ascending(true)
.weights_category(Some(WeightsCategory::ImageGeneration))
.limit(page_size)
.offset(Some(offset.into()));
let result = qb.perform_query_for_page(&pool).await?;
assert_eq!(result.weights.len(), page_size as usize);
assert_eq!(result.weights[0].weights_category, WeightsCategory::ImageGeneration);
Ok(())
}
}
@@ -9,37 +9,38 @@ pub struct UpdateWeightArgs<'a> {
pub weight_token: &'a ModelWeightToken,
pub title: Option<&'a str>,
pub maybe_thumbnail_token: Option<&'a str>,
pub description_markdown: &'a str,
pub description_rendered_html: &'a str,
pub creator_set_visibility: &'a Visibility,
pub description_markdown: Option<&'a str>,
pub description_rendered_html: Option<&'a str>,
pub creator_set_visibility: Option<&'a Visibility>,
pub weights_type: Option<String>,
pub weights_category: Option<String>,
pub mysql_pool: &'a MySqlPool,
}
pub async fn update_weights(args: UpdateWeightArgs<'_>) -> AnyhowResult<()> {
let mut transaction = args.mysql_pool.begin().await?;
let transaction = args.mysql_pool.begin().await?;
let query_result = sqlx
::query!(
r#"
UPDATE model_weights
SET
title = ?,
description_markdown = ?,
maybe_thumbnail_token = ?,
description_rendered_html = ?,
creator_set_visibility = ?,
version = version + 1
WHERE token = ?
LIMIT 1
"#,
args.title,
args.description_markdown,
args.maybe_thumbnail_token,
args.description_rendered_html,
args.creator_set_visibility.to_str(),
args.weight_token.as_str()
)
.execute(args.mysql_pool).await;
let query_result = sqlx::query!(
r#"
UPDATE model_weights
SET
title = COALESCE(?, title),
description_markdown = COALESCE(?, description_markdown),
maybe_thumbnail_token = COALESCE(?, maybe_thumbnail_token),
description_rendered_html = COALESCE(?, description_rendered_html),
creator_set_visibility = COALESCE(?, creator_set_visibility),
version = version + 1
WHERE token = ?
"#,
args.title.as_deref(),
args.description_markdown.as_deref(),
args.maybe_thumbnail_token.as_deref(),
args.description_rendered_html.as_deref(),
args.creator_set_visibility.as_deref(),
args.weight_token.as_str()
)
.execute(args.mysql_pool).await;
transaction.commit().await?;
@@ -53,6 +54,7 @@ pub async fn update_weights(args: UpdateWeightArgs<'_>) -> AnyhowResult<()> {
#[cfg(test)]
mod tests {
// Template
use sqlx::mysql::MySqlPoolOptions;
use tokio;
+1 -1
View File
@@ -19,7 +19,7 @@ errors = { path = "../errors" }
# External
bcrypt = "0.8" # password hashes
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
#hmac = "0.10.0" # jwt signing
#jwt = "0.13.0"
#sha2 = "0.9.5" # hmac signing
+1 -1
View File
@@ -27,4 +27,4 @@ serde_json = "1.0.108"
# TODO: Get rid of in favor of std::time
time = "0.3.30"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
+1 -1
View File
@@ -15,7 +15,7 @@ path = "src/lib.rs"
[dependencies]
# External
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
[dev-dependencies]
# None yet
+1 -6
View File
@@ -20,7 +20,7 @@ errors = { path = "../../lib/errors" }
tokens = { path = "../../_types/tokens" }
# External
chrono = { version = "=0.4.22", features = ["serde"] }
chrono = { version = "0.4.22", features = ["serde"] }
dotenv = "0.15.0"
log = "0.4.14"
@@ -28,11 +28,6 @@ log = "0.4.14"
once_cell = "1.9.0"
regex = "1.5.4"
# SQlite Database
# "offline" supports typesafe builds without a live database
# "rustls" doesn't appear to work with IP addresses, per:
# https://github.com/launchbadge/sqlx/issues/1095#issuecomment-798050828
# ^^^ Granted, this applies to *MySQL* and probably not SQLite for our use case here ^^^
sqlx = { version = "0.7.2", features = [ "sqlite", "runtime-tokio-native-tls", "chrono" ] }
[dev-dependencies]
+1 -5
View File
@@ -16,12 +16,8 @@ path = "src/lib.rs"
# Internal
errors = { path = "../errors" }
# MySql Database
# "offline" supports typesafe builds without a live database
# "rustls" doesn't appear to work with IP addresses, per:
# https://github.com/launchbadge/sqlx/issues/1095#issuecomment-798050828
sqlx = { version = "0.7.2", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
[dev-dependencies]
# None yet
+1 -1
View File
@@ -14,7 +14,7 @@ path = "src/lib.rs"
[dependencies]
# External
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
[dev-dependencies]
serial_test = "2.0.0"
+1 -1
View File
@@ -15,7 +15,7 @@ path = "src/lib.rs"
[dependencies]
# External
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
# Serialization
#serde = "1.0.125"
+1 -1
View File
@@ -18,7 +18,7 @@ path = "src/lib.rs"
# Internal
errors = { path = "../errors" }
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
# External
# None yet
+2 -2
View File
@@ -20,7 +20,7 @@ user_input_common = { path = "../user_input_common" }
# External
anyhow = "1.0.75"
chrono = { version = "=0.4.22", features = ["serde"] }
chrono = { version = "0.4.22", features = ["serde"] }
log = "0.4.14"
once_cell = "1.18.0"
regex = "1.5.4"
@@ -29,4 +29,4 @@ unicode-segmentation = "1.10.1"
# Serialization
serde = "1.0.125"
serde_derive = "1.0.125"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
+1 -1
View File
@@ -21,4 +21,4 @@ reusable_types = { path = "../../_types/reusable_types" }
# External
actix-web = "4.4.0"
anyhow = "1.0.75"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
+1 -1
View File
@@ -19,4 +19,4 @@ once_cell = "1.18.0"
pulldown-cmark = "0.9.3" # Markdown
regex = "1.5.4"
unicode-segmentation = "1.10.1"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
+2 -2
View File
@@ -19,7 +19,7 @@ errors = { path = "../../lib/errors" }
# External
bytes = "1.3"
chrono = { version = "=0.4.22", features = ["serde"] }
chrono = { version = "0.4.22", features = ["serde"] }
log = "0.4.14"
once_cell = "1.18.0"
strum = { version = "0.25", features = ["derive"] } # Fancy enum iteration, etc.
@@ -35,7 +35,7 @@ reqwest = { version = "0.11", features = ["json"] } # HTTP Client
# Scraping
rss = "2.0.2"
scraper = "0.17.1"
my-workspace-hack = { version = "0.1", path = "../../../my-workspace-hack" }
my-workspace-hack = { version = "0.1", path = "../../my-workspace-hack" }
[dev-dependencies]
# None yet
@@ -142,22 +142,22 @@ url = { version = "2.4.1", features = ["serde"] }
[target.x86_64-unknown-linux-gnu.dependencies]
openssl = { version = "0.10.59", features = ["vendored"] }
openssl-sys = { version = "0.9.95", default-features = false, features = ["vendored"] }
rustix = { version = "0.38.25", features = ["fs", "termios"] }
rustix = { version = "0.38.26", features = ["fs", "termios"] }
spin = { version = "0.9.8" }
[target.x86_64-unknown-linux-gnu.build-dependencies]
openssl = { version = "0.10.59", features = ["vendored"] }
openssl-sys = { version = "0.9.95", default-features = false, features = ["vendored"] }
rustix = { version = "0.38.25", features = ["fs", "termios"] }
rustix = { version = "0.38.26", features = ["fs", "termios"] }
socket2 = { version = "0.5.5", default-features = false, features = ["all"] }
spin = { version = "0.9.8" }
[target.x86_64-apple-darwin.dependencies]
rustix = { version = "0.38.25", features = ["fs", "termios"] }
rustix = { version = "0.38.26", features = ["fs", "termios"] }
spin = { version = "0.9.8" }
[target.x86_64-apple-darwin.build-dependencies]
rustix = { version = "0.38.25", features = ["fs", "termios"] }
rustix = { version = "0.38.26", features = ["fs", "termios"] }
socket2 = { version = "0.5.5", default-features = false, features = ["all"] }
spin = { version = "0.9.8" }
+1 -6
View File
@@ -32,7 +32,7 @@ web_scrapers = { path = "../../lib/web_scrapers" }
# External
base64 = "0.21.0"
chrono = { version = "=0.4.22", features = ["serde"] }
chrono = { version = "0.4.22", features = ["serde"] }
clap = "2.34.0" # TODO: This is really out of date, but has conflicts when updated
dotenv = "0.15.0"
log = "0.4.14"
@@ -42,11 +42,6 @@ rand = "0.8.3"
regex = "1.5.4"
strum = { version = "0.24", features = ["derive"] } # Fancy enum iteration, etc.
# SQlite Database
# "offline" supports typesafe builds without a live database
# "rustls" doesn't appear to work with IP addresses, per:
# https://github.com/launchbadge/sqlx/issues/1095#issuecomment-798050828
# ^^^ Granted, this applies to *MySQL* and probably not SQLite for our use case here ^^^
sqlx = { version = "0.7.2", features = [ "sqlite", "runtime-tokio-native-tls", "chrono" ] }
# Async Runtime

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