Compare commits

...
Author SHA1 Message Date
Brandon Thomas 638f958242 quality 2026-03-27 07:31:49 -04:00
Brandon Thomas e93c8b728f Add more resolution options 2026-03-26 16:00:12 -04:00
13 changed files with 262 additions and 2 deletions
@@ -14,4 +14,16 @@ pub enum CommonResolution {
ThreeK,
/// Models: Nano Banana Pro
FourK,
/// Used by Nano Banana 2 (and others)
HalfK,
/// Used by the Seedance family (and others)
FourEightyP,
/// Used by the Seedance family (and others)
SevenTwentyP,
/// Used by the Seedance family (and others)
TenEightyP,
}
@@ -135,6 +135,8 @@ fn plan_resolution(
// No 3K option; default to 2K
Ok(Some(Nb2Res::TwoK))
}
// HalfK, FourEightyP, SevenTwentyP, TenEightyP — nearest supported is OneK
Some(R::HalfK | R::FourEightyP | R::SevenTwentyP | R::TenEightyP) => Ok(Some(Nb2Res::OneK)),
}
}
@@ -135,6 +135,8 @@ fn plan_resolution(
// No 3K option; default to 2K
Ok(Some(NbpRes::TwoK))
}
// HalfK, FourEightyP, SevenTwentyP, TenEightyP — nearest supported is OneK
Some(R::HalfK | R::FourEightyP | R::SevenTwentyP | R::TenEightyP) => Ok(Some(NbpRes::OneK)),
}
}
@@ -202,6 +202,11 @@ fn plan_resolution(
Some(CommonResolution::FourK) => Ok(Some(FalNbpResolution::FourK)),
// No 3K option; fall back to 2K
Some(CommonResolution::ThreeK) => Ok(Some(FalNbpResolution::TwoK)),
// Fall back to nearest supported resolution
Some(CommonResolution::HalfK) => Ok(Some(FalNbpResolution::OneK)),
Some(CommonResolution::FourEightyP) => Ok(Some(FalNbpResolution::OneK)),
Some(CommonResolution::SevenTwentyP) => Ok(Some(FalNbpResolution::OneK)),
Some(CommonResolution::TenEightyP) => Ok(Some(FalNbpResolution::OneK)),
}
}
@@ -167,6 +167,10 @@ fn plan_resolution(
Some(CommonResolution::TwoK) => Ok(Some(Seedance1p5ProMultiFunctionVideoGenResolution::TenEightyP)),
Some(CommonResolution::ThreeK) => Ok(Some(Seedance1p5ProMultiFunctionVideoGenResolution::TenEightyP)),
Some(CommonResolution::FourK) => Ok(Some(Seedance1p5ProMultiFunctionVideoGenResolution::TenEightyP)),
// HalfK and FourEightyP map to the lowest supported resolution
Some(CommonResolution::HalfK | CommonResolution::FourEightyP) => Ok(Some(Seedance1p5ProMultiFunctionVideoGenResolution::FourEightyP)),
Some(CommonResolution::SevenTwentyP) => Ok(Some(Seedance1p5ProMultiFunctionVideoGenResolution::SevenTwentyP)),
Some(CommonResolution::TenEightyP) => Ok(Some(Seedance1p5ProMultiFunctionVideoGenResolution::TenEightyP)),
}
}
@@ -14,4 +14,16 @@ pub enum CommonResolution {
ThreeK,
/// Models: Nano Banana Pro
FourK,
/// Used by Nano Banana 2 (and others)
HalfK,
/// Used by the Seedance family (and others)
FourEightyP,
/// Used by the Seedance family (and others)
SevenTwentyP,
/// Used by the Seedance family (and others)
TenEightyP,
}
@@ -8,5 +8,10 @@ pub fn resolution_to_artcraft_nano_banana_pro(resolution: CommonResolution) -> N
// NB: ThreeK doesn't exist in Nano Banana Pro, use FourK as nearest higher resolution
CommonResolution::ThreeK => NanoBananaProMultiFunctionImageGenImageResolution::FourK,
CommonResolution::FourK => NanoBananaProMultiFunctionImageGenImageResolution::FourK,
// Fall back to nearest supported resolution
CommonResolution::HalfK => NanoBananaProMultiFunctionImageGenImageResolution::OneK,
CommonResolution::FourEightyP => NanoBananaProMultiFunctionImageGenImageResolution::OneK,
CommonResolution::SevenTwentyP => NanoBananaProMultiFunctionImageGenImageResolution::OneK,
CommonResolution::TenEightyP => NanoBananaProMultiFunctionImageGenImageResolution::OneK,
}
}
@@ -144,5 +144,9 @@ fn convert_desktop_resolution(res: CommonResolution2) -> RouterCommonResolution
CommonResolution2::TwoK => RouterCommonResolution::TwoK,
CommonResolution2::ThreeK => RouterCommonResolution::ThreeK,
CommonResolution2::FourK => RouterCommonResolution::FourK,
CommonResolution2::HalfK => RouterCommonResolution::HalfK,
CommonResolution2::FourEightyP => RouterCommonResolution::FourEightyP,
CommonResolution2::SevenTwentyP => RouterCommonResolution::SevenTwentyP,
CommonResolution2::TenEightyP => RouterCommonResolution::TenEightyP,
}
}
@@ -0,0 +1,167 @@
use std::collections::BTreeSet;
#[cfg(test)]
use strum::EnumCount;
#[cfg(test)]
use strum::EnumIter;
use utoipa::ToSchema;
use crate::error::enum_error::EnumError;
/// Maximum serialized string length for database storage.
pub const MAX_LENGTH: usize = 16;
/// Common quality levels for generation.
///
/// NB: Keep the max serialized length to 16 characters.
#[cfg_attr(test, derive(EnumIter, EnumCount))]
#[derive(Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum CommonQuality {
High,
Medium,
Low,
}
impl_enum_display_and_debug_using_to_str!(CommonQuality);
impl_mysql_enum_coders!(CommonQuality);
impl_mysql_from_row!(CommonQuality);
impl CommonQuality {
pub fn to_str(&self) -> &'static str {
match self {
Self::High => "high",
Self::Medium => "medium",
Self::Low => "low",
}
}
pub fn from_str(value: &str) -> Result<Self, EnumError> {
match value {
"high" => Ok(Self::High),
"medium" => Ok(Self::Medium),
"low" => Ok(Self::Low),
_ => Err(EnumError::CouldNotConvertFromString(value.to_string())),
}
}
pub fn all_variants() -> BTreeSet<Self> {
BTreeSet::from([
Self::High,
Self::Medium,
Self::Low,
])
}
}
#[cfg(test)]
mod tests {
use crate::common::generation::common_quality::CommonQuality;
use crate::common::generation::common_quality::MAX_LENGTH;
use crate::test_helpers::assert_serialization;
mod explicit_checks {
use super::*;
use crate::error::enum_error::EnumError;
#[test]
fn test_serialization() {
assert_serialization(CommonQuality::High, "high");
assert_serialization(CommonQuality::Medium, "medium");
assert_serialization(CommonQuality::Low, "low");
}
#[test]
fn to_str() {
assert_eq!(CommonQuality::High.to_str(), "high");
assert_eq!(CommonQuality::Medium.to_str(), "medium");
assert_eq!(CommonQuality::Low.to_str(), "low");
}
#[test]
fn from_str() {
assert_eq!(CommonQuality::from_str("high").unwrap(), CommonQuality::High);
assert_eq!(CommonQuality::from_str("medium").unwrap(), CommonQuality::Medium);
assert_eq!(CommonQuality::from_str("low").unwrap(), CommonQuality::Low);
}
#[test]
fn from_str_err() {
let result = CommonQuality::from_str("invalid");
assert!(result.is_err());
if let Err(EnumError::CouldNotConvertFromString(value)) = result {
assert_eq!(value, "invalid");
} else {
panic!("Expected EnumError::CouldNotConvertFromString");
}
}
#[test]
fn all_variants() {
let mut variants = CommonQuality::all_variants();
assert_eq!(variants.len(), 3);
assert_eq!(variants.pop_first(), Some(CommonQuality::High));
assert_eq!(variants.pop_first(), Some(CommonQuality::Medium));
assert_eq!(variants.pop_first(), Some(CommonQuality::Low));
assert_eq!(variants.pop_first(), None);
}
}
mod mechanical_checks {
use super::*;
#[test]
fn variant_length() {
use strum::IntoEnumIterator;
assert_eq!(CommonQuality::all_variants().len(), CommonQuality::iter().len());
}
#[test]
fn round_trip() {
for variant in CommonQuality::all_variants() {
assert_eq!(variant, CommonQuality::from_str(variant.to_str()).unwrap());
assert_eq!(variant, CommonQuality::from_str(&format!("{}", variant)).unwrap());
assert_eq!(variant, CommonQuality::from_str(&format!("{:?}", variant)).unwrap());
}
}
#[test]
fn serialized_length_ok_for_database() {
for variant in CommonQuality::all_variants() {
let serialized = variant.to_str();
assert!(serialized.len() > 0, "variant {:?} is too short", variant);
assert!(serialized.len() <= MAX_LENGTH, "variant {:?} is too long via to_str()", variant);
}
for variant in CommonQuality::all_variants() {
let json = serde_json::to_string(&variant).unwrap().replace('"', "");
assert!(json.len() <= MAX_LENGTH, "variant {:?} is too long via JSON: {:?}", variant, json);
}
}
#[test]
fn serialized_names_must_not_contain_dots() {
for variant in CommonQuality::all_variants() {
let to_str_value = variant.to_str();
assert!(!to_str_value.contains('.'), "to_str() for {:?} contains a dot: {:?}", variant, to_str_value);
let json_value = serde_json::to_string(&variant).unwrap().replace('"', "");
assert!(!json_value.contains('.'), "JSON serialization for {:?} contains a dot: {:?}", variant, json_value);
}
}
#[test]
fn serialized_names_must_only_contain_lowercase_alphanumeric_and_underscore() {
let valid_pattern = regex::Regex::new(r"^[a-z0-9_]+$").unwrap();
for variant in CommonQuality::all_variants() {
let to_str_value = variant.to_str();
assert!(valid_pattern.is_match(to_str_value),
"to_str() for {:?} contains invalid characters: {:?} (only a-z, 0-9, _ allowed)", variant, to_str_value);
let json_value = serde_json::to_string(&variant).unwrap().replace('"', "");
assert!(valid_pattern.is_match(&json_value),
"JSON serialization for {:?} contains invalid characters: {:?} (only a-z, 0-9, _ allowed)", variant, json_value);
}
}
}
}
@@ -23,7 +23,17 @@ pub enum CommonResolution {
ThreeK,
FourK,
// TODO: Add 480p(?), 720p(?), 1080p, etc. as there are some models that use these
/// Used by Nano Banana 2 (and others)
HalfK,
/// Used by the Seedance family (and others)
FourEightyP,
/// Used by the Seedance family (and others)
SevenTwentyP,
/// Used by the Seedance family (and others)
TenEightyP,
}
impl_enum_display_and_debug_using_to_str!(CommonResolution);
@@ -37,6 +47,10 @@ impl CommonResolution {
Self::TwoK => "two_k",
Self::ThreeK => "three_k",
Self::FourK => "four_k",
Self::HalfK => "half_k",
Self::FourEightyP => "four_eighty_p",
Self::SevenTwentyP => "seven_twenty_p",
Self::TenEightyP => "ten_eighty_p",
}
}
@@ -46,6 +60,10 @@ impl CommonResolution {
"two_k" => Ok(Self::TwoK),
"three_k" => Ok(Self::ThreeK),
"four_k" => Ok(Self::FourK),
"half_k" => Ok(Self::HalfK),
"four_eighty_p" => Ok(Self::FourEightyP),
"seven_twenty_p" => Ok(Self::SevenTwentyP),
"ten_eighty_p" => Ok(Self::TenEightyP),
_ => Err(EnumError::CouldNotConvertFromString(value.to_string())),
}
}
@@ -56,6 +74,10 @@ impl CommonResolution {
Self::TwoK,
Self::ThreeK,
Self::FourK,
Self::HalfK,
Self::FourEightyP,
Self::SevenTwentyP,
Self::TenEightyP,
])
}
}
@@ -76,6 +98,10 @@ mod tests {
assert_serialization(CommonResolution::TwoK, "two_k");
assert_serialization(CommonResolution::ThreeK, "three_k");
assert_serialization(CommonResolution::FourK, "four_k");
assert_serialization(CommonResolution::HalfK, "half_k");
assert_serialization(CommonResolution::FourEightyP, "four_eighty_p");
assert_serialization(CommonResolution::SevenTwentyP, "seven_twenty_p");
assert_serialization(CommonResolution::TenEightyP, "ten_eighty_p");
}
#[test]
@@ -84,6 +110,10 @@ mod tests {
assert_eq!(CommonResolution::TwoK.to_str(), "two_k");
assert_eq!(CommonResolution::ThreeK.to_str(), "three_k");
assert_eq!(CommonResolution::FourK.to_str(), "four_k");
assert_eq!(CommonResolution::HalfK.to_str(), "half_k");
assert_eq!(CommonResolution::FourEightyP.to_str(), "four_eighty_p");
assert_eq!(CommonResolution::SevenTwentyP.to_str(), "seven_twenty_p");
assert_eq!(CommonResolution::TenEightyP.to_str(), "ten_eighty_p");
}
#[test]
@@ -92,6 +122,10 @@ mod tests {
assert_eq!(CommonResolution::from_str("two_k").unwrap(), CommonResolution::TwoK);
assert_eq!(CommonResolution::from_str("three_k").unwrap(), CommonResolution::ThreeK);
assert_eq!(CommonResolution::from_str("four_k").unwrap(), CommonResolution::FourK);
assert_eq!(CommonResolution::from_str("half_k").unwrap(), CommonResolution::HalfK);
assert_eq!(CommonResolution::from_str("four_eighty_p").unwrap(), CommonResolution::FourEightyP);
assert_eq!(CommonResolution::from_str("seven_twenty_p").unwrap(), CommonResolution::SevenTwentyP);
assert_eq!(CommonResolution::from_str("ten_eighty_p").unwrap(), CommonResolution::TenEightyP);
}
#[test]
@@ -108,11 +142,15 @@ mod tests {
#[test]
fn all_variants() {
let mut variants = CommonResolution::all_variants();
assert_eq!(variants.len(), 4);
assert_eq!(variants.len(), 8);
assert_eq!(variants.pop_first(), Some(CommonResolution::OneK));
assert_eq!(variants.pop_first(), Some(CommonResolution::TwoK));
assert_eq!(variants.pop_first(), Some(CommonResolution::ThreeK));
assert_eq!(variants.pop_first(), Some(CommonResolution::FourK));
assert_eq!(variants.pop_first(), Some(CommonResolution::HalfK));
assert_eq!(variants.pop_first(), Some(CommonResolution::FourEightyP));
assert_eq!(variants.pop_first(), Some(CommonResolution::SevenTwentyP));
assert_eq!(variants.pop_first(), Some(CommonResolution::TenEightyP));
assert_eq!(variants.pop_first(), None);
}
}
@@ -1,6 +1,7 @@
pub mod common_aspect_ratio;
pub mod common_generation_mode;
pub mod common_image_model;
pub mod common_quality;
pub mod common_model_class;
pub mod common_model_type;
pub mod common_resolution;
@@ -168,5 +168,9 @@ fn map_resolution(res: CommonResolution) -> RouterResolution {
CommonResolution::TwoK => RouterResolution::TwoK,
CommonResolution::ThreeK => RouterResolution::ThreeK,
CommonResolution::FourK => RouterResolution::FourK,
CommonResolution::HalfK => RouterResolution::HalfK,
CommonResolution::FourEightyP => RouterResolution::FourEightyP,
CommonResolution::SevenTwentyP => RouterResolution::SevenTwentyP,
CommonResolution::TenEightyP => RouterResolution::TenEightyP,
}
}
@@ -178,5 +178,9 @@ fn map_resolution(res: CommonResolution) -> RouterResolution {
CommonResolution::TwoK => RouterResolution::TwoK,
CommonResolution::ThreeK => RouterResolution::ThreeK,
CommonResolution::FourK => RouterResolution::FourK,
CommonResolution::HalfK => RouterResolution::HalfK,
CommonResolution::FourEightyP => RouterResolution::FourEightyP,
CommonResolution::SevenTwentyP => RouterResolution::SevenTwentyP,
CommonResolution::TenEightyP => RouterResolution::TenEightyP,
}
}