mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-03 13:02:37 -05:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bc1c62c65 | ||
|
|
a4d9a4d199 | ||
|
|
2b8cc5ef8d | ||
|
|
9122be6752 | ||
|
|
31054141cc |
+26
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n users.token as `user_token: UserToken`,\n users.username,\n users.display_name,\n users.email_address,\n users.user_role_slug,\n users.is_banned,\n user_roles.can_ban_users\nFROM user_sessions\nINNER JOIN users\n ON users.token = user_sessions.user_token\nLEFT OUTER JOIN user_roles\n ON users.user_role_slug = user_roles.slug\nWHERE user_sessions.token = ?\n AND user_sessions.deleted_at IS NULL\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\nLIMIT 1\n ",
|
||||
"query": "\nSELECT\n users.token as `user_token: UserToken`,\n users.username,\n users.display_name,\n users.email_address,\n users.user_role_slug,\n users.is_banned,\n user_roles.can_ban_users,\n users.maybe_feature_flags,\n api_keys.token as `api_key_token: ApiKeyToken`\nFROM api_keys\nINNER JOIN users\n ON users.token = api_keys.owner_user_token\nLEFT OUTER JOIN user_roles\n ON users.user_role_slug = user_roles.slug\nWHERE api_keys.api_key = ?\n AND api_keys.maybe_deleted_at IS NULL\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -72,6 +72,26 @@
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "maybe_feature_flags",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "api_key_token: ApiKeyToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -84,8 +104,10 @@
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
true,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "c77126adb36f9e2f0248720eb4d8c1cd9d14719f64a02678d8021e26addd2c18"
|
||||
}
|
||||
"hash": "62b3e2e058331a6819acd98c634b3bf2d10aa38d43b49ab69fb8637497beec2f"
|
||||
}
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n users.token as `user_token: UserToken`,\n users.username,\n users.display_name,\n users.email_address,\n users.user_role_slug,\n users.is_banned,\n user_roles.can_ban_users,\n api_keys.token as `api_key_token: ApiKeyToken`\nFROM api_keys\nINNER JOIN users\n ON users.token = api_keys.owner_user_token\nLEFT OUTER JOIN user_roles\n ON users.user_role_slug = user_roles.slug\nWHERE api_keys.api_key = ?\n AND api_keys.maybe_deleted_at IS NULL\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\nLIMIT 1\n ",
|
||||
"query": "\nSELECT\n users.token as `user_token: UserToken`,\n users.username,\n users.display_name,\n users.email_address,\n users.user_role_slug,\n users.is_banned,\n user_roles.can_ban_users,\n users.maybe_feature_flags\nFROM user_sessions\nINNER JOIN users\n ON users.token = user_sessions.user_token\nLEFT OUTER JOIN user_roles\n ON users.user_role_slug = user_roles.slug\nWHERE user_sessions.token = ?\n AND user_sessions.deleted_at IS NULL\n AND users.user_deleted_at IS NULL\n AND users.mod_deleted_at IS NULL\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -75,12 +75,12 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "api_key_token: ApiKeyToken",
|
||||
"name": "maybe_feature_flags",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
"max_size": 1020
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -95,8 +95,8 @@
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "9188e235caa439d50628333c2f4722b9f1a8f575c192d4d36a2039709c412ac0"
|
||||
}
|
||||
"hash": "cf2794c87868e77dfaa45bdbe630ae276dbb8c2d041442ba465af0b7a6d61af6"
|
||||
}
|
||||
+7
-5
@@ -16,7 +16,9 @@ impl ArtcraftQwenEdit2511AnglesCostState {
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> ImageGenerationCostEstimate {
|
||||
let cost_in_usd_cents = 4u64 * self.num_images as u64;
|
||||
// 4.6¢ per output image (held in hundredths of a cent, rounded up to
|
||||
// whole cents across the batch).
|
||||
let cost_in_usd_cents = (460u64 * self.num_images as u64).div_ceil(100);
|
||||
ImageGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_usd_cents),
|
||||
cost_in_usd_cents: Some(cost_in_usd_cents),
|
||||
@@ -59,14 +61,14 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_image_costs_4_cents() { assert_eq!(cost_cents(1), 4); }
|
||||
fn one_image_costs_5_cents() { assert_eq!(cost_cents(1), 5); }
|
||||
|
||||
#[test]
|
||||
fn two_images_costs_8_cents() { assert_eq!(cost_cents(2), 8); }
|
||||
fn two_images_costs_10_cents() { assert_eq!(cost_cents(2), 10); }
|
||||
|
||||
#[test]
|
||||
fn three_images_costs_12_cents() { assert_eq!(cost_cents(3), 12); }
|
||||
fn three_images_costs_14_cents() { assert_eq!(cost_cents(3), 14); }
|
||||
|
||||
#[test]
|
||||
fn four_images_costs_16_cents() { assert_eq!(cost_cents(4), 16); }
|
||||
fn four_images_costs_19_cents() { assert_eq!(cost_cents(4), 19); }
|
||||
}
|
||||
|
||||
+5
-5
@@ -15,8 +15,8 @@ impl ArtcraftKling16ProCostState {
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// 5s = 52¢, 10s = 103¢.
|
||||
let cost_in_usd_cents: u64 = if self.is_ten_seconds { 103 } else { 52 };
|
||||
// 5s = 57¢, 10s = 113¢.
|
||||
let cost_in_usd_cents: u64 = if self.is_ten_seconds { 113 } else { 57 };
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_usd_cents),
|
||||
@@ -51,11 +51,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn five_seconds_is_52() { assert_eq!(cost_cents(Some(5)), 52); }
|
||||
fn five_seconds_is_57() { assert_eq!(cost_cents(Some(5)), 57); }
|
||||
|
||||
#[test]
|
||||
fn ten_seconds_is_103() { assert_eq!(cost_cents(Some(10)), 103); }
|
||||
fn ten_seconds_is_113() { assert_eq!(cost_cents(Some(10)), 113); }
|
||||
|
||||
#[test]
|
||||
fn default_duration_is_5s_priced_at_52() { assert_eq!(cost_cents(None), 52); }
|
||||
fn default_duration_is_5s_priced_at_57() { assert_eq!(cost_cents(None), 57); }
|
||||
}
|
||||
|
||||
+4
-4
@@ -13,7 +13,7 @@ impl ArtcraftKling21MasterCostState {
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// Mirrors fal_client kling_v2p1_master: 5s = $1.40, 10s = $2.80.
|
||||
let cost_in_usd_cents: u64 = if self.is_ten_seconds { 280 } else { 140 };
|
||||
let cost_in_usd_cents: u64 = if self.is_ten_seconds { 322 } else { 161 };
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_usd_cents),
|
||||
@@ -48,11 +48,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn five_seconds_is_140() { assert_eq!(cost_cents(Some(5)), 140); }
|
||||
fn five_seconds_is_161() { assert_eq!(cost_cents(Some(5)), 161); }
|
||||
|
||||
#[test]
|
||||
fn ten_seconds_is_280() { assert_eq!(cost_cents(Some(10)), 280); }
|
||||
fn ten_seconds_is_322() { assert_eq!(cost_cents(Some(10)), 322); }
|
||||
|
||||
#[test]
|
||||
fn default_duration_is_5s_priced_at_140() { assert_eq!(cost_cents(None), 140); }
|
||||
fn default_duration_is_5s_priced_at_161() { assert_eq!(cost_cents(None), 161); }
|
||||
}
|
||||
|
||||
+4
-4
@@ -13,7 +13,7 @@ impl ArtcraftKling21ProCostState {
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// Mirrors fal_client kling_v2p1_pro: 5s = 45¢, 10s = 90¢.
|
||||
let cost_in_usd_cents: u64 = if self.is_ten_seconds { 90 } else { 45 };
|
||||
let cost_in_usd_cents: u64 = if self.is_ten_seconds { 104 } else { 52 };
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_usd_cents),
|
||||
@@ -48,11 +48,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn five_seconds_is_45() { assert_eq!(cost_cents(Some(5)), 45); }
|
||||
fn five_seconds_is_52() { assert_eq!(cost_cents(Some(5)), 52); }
|
||||
|
||||
#[test]
|
||||
fn ten_seconds_is_90() { assert_eq!(cost_cents(Some(10)), 90); }
|
||||
fn ten_seconds_is_104() { assert_eq!(cost_cents(Some(10)), 104); }
|
||||
|
||||
#[test]
|
||||
fn default_duration_is_5s_priced_at_45() { assert_eq!(cost_cents(None), 45); }
|
||||
fn default_duration_is_5s_priced_at_52() { assert_eq!(cost_cents(None), 52); }
|
||||
}
|
||||
|
||||
+4
-4
@@ -13,7 +13,7 @@ impl ArtcraftKling2p5TurboProCostState {
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// Mirrors fal_client kling_v2p5_turbo_pro: 5s = 35¢, 10s = 70¢.
|
||||
let cost_in_usd_cents: u64 = if self.is_ten_seconds { 70 } else { 35 };
|
||||
let cost_in_usd_cents: u64 = if self.is_ten_seconds { 81 } else { 41 };
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_usd_cents),
|
||||
@@ -45,11 +45,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn five_seconds_is_35() { assert_eq!(cost_cents(Some(5)), 35); }
|
||||
fn five_seconds_is_41() { assert_eq!(cost_cents(Some(5)), 41); }
|
||||
|
||||
#[test]
|
||||
fn ten_seconds_is_70() { assert_eq!(cost_cents(Some(10)), 70); }
|
||||
fn ten_seconds_is_81() { assert_eq!(cost_cents(Some(10)), 81); }
|
||||
|
||||
#[test]
|
||||
fn default_duration_is_5s_priced_at_35() { assert_eq!(cost_cents(None), 35); }
|
||||
fn default_duration_is_5s_priced_at_41() { assert_eq!(cost_cents(None), 41); }
|
||||
}
|
||||
|
||||
+8
-8
@@ -21,10 +21,10 @@ impl ArtcraftKling2p6ProCostState {
|
||||
// audio off: $0.07/sec → 5s=35¢, 10s=70¢
|
||||
// audio on: $0.14/sec → 5s=70¢, 10s=140¢
|
||||
let cost_in_usd_cents: u64 = match (self.generate_audio, self.is_ten_seconds) {
|
||||
(false, false) => 35,
|
||||
(false, true) => 70,
|
||||
(true, false) => 70,
|
||||
(true, true) => 140,
|
||||
(false, false) => 41,
|
||||
(false, true) => 81,
|
||||
(true, false) => 81,
|
||||
(true, true) => 161,
|
||||
};
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
@@ -58,16 +58,16 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_off_5s_is_35() { assert_eq!(cost_cents(Some(5), Some(false)), 35); }
|
||||
fn audio_off_5s_is_41() { assert_eq!(cost_cents(Some(5), Some(false)), 41); }
|
||||
|
||||
#[test]
|
||||
fn audio_off_10s_is_70() { assert_eq!(cost_cents(Some(10), Some(false)), 70); }
|
||||
fn audio_off_10s_is_81() { assert_eq!(cost_cents(Some(10), Some(false)), 81); }
|
||||
|
||||
#[test]
|
||||
fn audio_on_5s_is_70() { assert_eq!(cost_cents(Some(5), Some(true)), 70); }
|
||||
fn audio_on_5s_is_81() { assert_eq!(cost_cents(Some(5), Some(true)), 81); }
|
||||
|
||||
#[test]
|
||||
fn audio_on_10s_is_140() { assert_eq!(cost_cents(Some(10), Some(true)), 140); }
|
||||
fn audio_on_10s_is_161() { assert_eq!(cost_cents(Some(10), Some(true)), 161); }
|
||||
|
||||
#[test]
|
||||
fn audio_default_is_on() {
|
||||
|
||||
+12
-9
@@ -1,4 +1,8 @@
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
|
||||
// The markup is applied at cent granularity with ceiling rounding.
|
||||
const MARKUP_NUMERATOR: u64 = 115;
|
||||
const MARKUP_DENOMINATOR: u64 = 100;
|
||||
use crate::generate::generate_video::providers::artcraft::kling_3p0_pro::request::ArtcraftKling3p0ProRequestState;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -17,12 +21,11 @@ impl ArtcraftKling3p0ProCostState {
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// Mirrors fal_client kling_3p0_pro:
|
||||
// audio off: $0.224/sec (rate=224 in tenths-of-cents)
|
||||
// audio on: $0.336/sec (rate=336)
|
||||
// ceiling-divided to whole cents.
|
||||
// Base rates in tenths of a cent per second; the total is
|
||||
// ceiling-divided to whole cents, then the markup applied.
|
||||
let rate: u64 = if self.generate_audio { 336 } else { 224 };
|
||||
let cost_in_usd_cents = (rate * self.duration_seconds + 9) / 10;
|
||||
let base = (rate * self.duration_seconds + 9) / 10;
|
||||
let cost_in_usd_cents = base.saturating_mul(MARKUP_NUMERATOR).div_ceil(MARKUP_DENOMINATOR);
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_usd_cents),
|
||||
@@ -57,24 +60,24 @@ mod tests {
|
||||
#[test]
|
||||
fn audio_on_5s_is_168() {
|
||||
// rate=336, (336*5 + 9) / 10 = 1689/10 = 168.
|
||||
assert_eq!(cost_cents(Some(5), Some(true)), 168);
|
||||
assert_eq!(cost_cents(Some(5), Some(true)), 194);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_off_5s_is_112() {
|
||||
// rate=224, (224*5 + 9) / 10 = 1129/10 = 112.
|
||||
assert_eq!(cost_cents(Some(5), Some(false)), 112);
|
||||
assert_eq!(cost_cents(Some(5), Some(false)), 129);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_on_10s_is_336() {
|
||||
assert_eq!(cost_cents(Some(10), Some(true)), 336);
|
||||
assert_eq!(cost_cents(Some(10), Some(true)), 387);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_on_15s_is_504() {
|
||||
// rate=336, (336*15 + 9) / 10 = 5049/10 = 504.
|
||||
assert_eq!(cost_cents(Some(15), Some(true)), 504);
|
||||
assert_eq!(cost_cents(Some(15), Some(true)), 580);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+19
-15
@@ -1,6 +1,10 @@
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
use crate::generate::generate_video::providers::artcraft::kling_3p0_standard::request::ArtcraftKling3p0StandardRequestState;
|
||||
|
||||
// The markup is applied at cent granularity with ceiling rounding.
|
||||
const MARKUP_NUMERATOR: u64 = 115;
|
||||
const MARKUP_DENOMINATOR: u64 = 100;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ArtcraftKling3p0StandardCostState {
|
||||
pub duration_seconds: u64,
|
||||
@@ -16,11 +20,11 @@ impl ArtcraftKling3p0StandardCostState {
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// Mirrors fal_client kling_3p0_standard:
|
||||
// audio off: $0.168/sec (rate=168)
|
||||
// audio on: $0.252/sec (rate=252)
|
||||
// Base rates in tenths of a cent per second; the total is
|
||||
// ceiling-divided to whole cents, then the markup applied.
|
||||
let rate: u64 = if self.generate_audio { 252 } else { 168 };
|
||||
let cost_in_usd_cents = (rate * self.duration_seconds + 9) / 10;
|
||||
let base = (rate * self.duration_seconds + 9) / 10;
|
||||
let cost_in_usd_cents = base.saturating_mul(MARKUP_NUMERATOR).div_ceil(MARKUP_DENOMINATOR);
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_usd_cents),
|
||||
@@ -53,26 +57,26 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_on_5s_is_126() {
|
||||
// rate=252, (252*5 + 9) / 10 = 1269/10 = 126.
|
||||
assert_eq!(cost_cents(Some(5), Some(true)), 126);
|
||||
fn audio_on_5s_is_145() {
|
||||
// rate=252, base (252*5 + 9) / 10 = 126, ×115/100 ceiled = 145.
|
||||
assert_eq!(cost_cents(Some(5), Some(true)), 145);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_off_5s_is_84() {
|
||||
// rate=168, (168*5 + 9) / 10 = 849/10 = 84.
|
||||
assert_eq!(cost_cents(Some(5), Some(false)), 84);
|
||||
fn audio_off_5s_is_97() {
|
||||
// rate=168, base (168*5 + 9) / 10 = 84, ×115/100 ceiled = 97.
|
||||
assert_eq!(cost_cents(Some(5), Some(false)), 97);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_on_10s_is_252() {
|
||||
assert_eq!(cost_cents(Some(10), Some(true)), 252);
|
||||
fn audio_on_10s_is_290() {
|
||||
assert_eq!(cost_cents(Some(10), Some(true)), 290);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_on_15s_is_378() {
|
||||
// (252*15 + 9) / 10 = 3789/10 = 378.
|
||||
assert_eq!(cost_cents(Some(15), Some(true)), 378);
|
||||
fn audio_on_15s_is_435() {
|
||||
// base (252*15 + 9) / 10 = 378, ×115/100 ceiled = 435.
|
||||
assert_eq!(cost_cents(Some(15), Some(true)), 435);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+10
-5
@@ -8,6 +8,10 @@ use fal_client::requests_old::webhook::video::text::enqueue_seedance_1p5_pro_tex
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
use crate::generate::generate_video::providers::artcraft::seedance_1p5_pro::request::ArtcraftSeedance1p5ProRequestState;
|
||||
|
||||
// The markup is applied at cent granularity with ceiling rounding.
|
||||
const MARKUP_NUMERATOR: u64 = 115;
|
||||
const MARKUP_DENOMINATOR: u64 = 100;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ArtcraftSeedance1p5ProCostState {
|
||||
pub duration: Option<EnqueueSeedance1p5ProTextToVideoDuration>,
|
||||
@@ -37,7 +41,8 @@ impl ArtcraftSeedance1p5ProCostState {
|
||||
aspect_ratio: None,
|
||||
generate_audio: self.generate_audio,
|
||||
};
|
||||
let cost_in_usd_cents = req.calculate_cost_in_cents();
|
||||
let base = req.calculate_cost_in_cents();
|
||||
let cost_in_usd_cents = base.saturating_mul(MARKUP_NUMERATOR).div_ceil(MARKUP_DENOMINATOR);
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_usd_cents),
|
||||
@@ -96,14 +101,14 @@ mod tests {
|
||||
// Numeric literals mirror the v1 Artcraft Seedance 1.5 Pro tests we deleted:
|
||||
// resolution=None + generate_audio=None → fal client defaults (720p, audio on).
|
||||
#[test]
|
||||
fn default_5s_is_26() { assert_eq!(cost_cents(Some(5)), 26); }
|
||||
fn default_5s_is_30() { assert_eq!(cost_cents(Some(5)), 30); }
|
||||
|
||||
#[test]
|
||||
fn default_4s_is_26() { assert_eq!(cost_cents(Some(4)), 26); }
|
||||
fn default_4s_is_30() { assert_eq!(cost_cents(Some(4)), 30); }
|
||||
|
||||
#[test]
|
||||
fn default_10s_is_65() { assert_eq!(cost_cents(Some(10)), 65); }
|
||||
fn default_10s_is_75() { assert_eq!(cost_cents(Some(10)), 75); }
|
||||
|
||||
#[test]
|
||||
fn default_12s_is_78() { assert_eq!(cost_cents(Some(12)), 78); }
|
||||
fn default_12s_is_90() { assert_eq!(cost_cents(Some(12)), 90); }
|
||||
}
|
||||
|
||||
+173
-17
@@ -8,25 +8,25 @@ use crate::generate::generate_video::video_generation_cost_estimate::VideoGenera
|
||||
//
|
||||
// ArtCraft credits: 100 credits = $1.00. Credits always equal USD cents.
|
||||
//
|
||||
// The per-second USD cost varies by resolution. We compute cents directly
|
||||
// from the upstream credit rates and their credit-package prices,
|
||||
// then set ArtCraft credits = cents.
|
||||
// The per-second USD price varies by resolution.
|
||||
|
||||
/// USD cents per second by resolution, derived from upstream rates:
|
||||
/// 480p: 15 upstream-credits/sec ÷ 193 upstream-credits/$1 × 100 ≈ 7.772 ¢/s
|
||||
/// 720p: 40 upstream-credits/sec ÷ 250 upstream-credits/$1 × 100 = 16.0 ¢/s
|
||||
/// 1080p: 90 upstream-credits/sec ÷ 193 upstream-credits/$1 × 100 ≈ 46.632 ¢/s
|
||||
/// USD cents per second by resolution.
|
||||
///
|
||||
/// We keep these as f64 because per-second rates are fractional; rounding
|
||||
/// happens once at the end after multiplying by duration × batch.
|
||||
///
|
||||
/// NB: Derivation is historical (193 credits/$1 package). The upstream
|
||||
/// package is now 525,000 credits for $2,159.0909 (~243 credits/$1), but
|
||||
/// user pricing is intentionally kept at these 193-derived rates.
|
||||
/// Kept as f64 because per-second rates are fractional; rounding happens
|
||||
/// once at the end after multiplying by duration × batch.
|
||||
const CENTS_PER_SECOND_480P: f64 = 7.772;
|
||||
const CENTS_PER_SECOND_720P: f64 = 16.0;
|
||||
const CENTS_PER_SECOND_1080P: f64 = 46.632;
|
||||
|
||||
/// USD cents per second, in hundredths of a cent, when one or more
|
||||
/// reference videos are attached. Held as integer hundredths so the math is
|
||||
/// exact; rounded up to whole cents once, after multiplying by
|
||||
/// duration × batch.
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P: u64 = 881;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P: u64 = 2_205;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_1080P: u64 = 5_110;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_4K: u64 = 11_380;
|
||||
|
||||
pub struct ArtcraftSeedance2p0CostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
@@ -48,6 +48,19 @@ impl ArtcraftSeedance2p0CostState {
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
if self.has_video_reference {
|
||||
let usd_cents = self.with_video_reference_usd_cents();
|
||||
return VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(usd_cents),
|
||||
cost_in_usd_cents: Some(usd_cents),
|
||||
is_free: false,
|
||||
is_unlimited: false,
|
||||
is_rate_limited: false,
|
||||
has_watermark: false,
|
||||
failures_are_refunded: None,
|
||||
};
|
||||
}
|
||||
|
||||
if self.resolution == CommonResolution::FourK {
|
||||
let usd_cents = seedance_2p0_four_k_usd_cents(
|
||||
self.duration_seconds,
|
||||
@@ -85,12 +98,31 @@ impl ArtcraftSeedance2p0CostState {
|
||||
failures_are_refunded: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Price when one or more reference videos are attached.
|
||||
fn with_video_reference_usd_cents(&self) -> u64 {
|
||||
let centi_cents_per_second = match self.resolution {
|
||||
CommonResolution::FourEightyP => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P,
|
||||
CommonResolution::TenEightyP => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_1080P,
|
||||
CommonResolution::FourK => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_4K,
|
||||
// Everything else (including 720p) prices at 720p.
|
||||
_ => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P,
|
||||
};
|
||||
|
||||
let seconds = self.duration_seconds as u64 * self.batch_count as u64;
|
||||
|
||||
// Round up to whole cents.
|
||||
(centi_cents_per_second * seconds).div_ceil(100)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::api::router_provider::RouterProvider;
|
||||
use crate::api::router_resolution::RouterResolution;
|
||||
use crate::api::video_list_ref::VideoListRef;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
|
||||
// -- 720p pricing --
|
||||
@@ -202,10 +234,110 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn explicit_4k_with_video_reference() {
|
||||
assert_eq!(artcraft_4k_cents(4, 1, true), 416);
|
||||
assert_eq!(artcraft_4k_cents(5, 1, true), 519);
|
||||
assert_eq!(artcraft_4k_cents(10, 1, true), 1038);
|
||||
assert_eq!(artcraft_4k_cents(15, 1, true), 1557);
|
||||
assert_eq!(artcraft_4k_cents(4, 1, true), 456);
|
||||
assert_eq!(artcraft_4k_cents(5, 1, true), 569);
|
||||
assert_eq!(artcraft_4k_cents(10, 1, true), 1138);
|
||||
assert_eq!(artcraft_4k_cents(15, 1, true), 1707);
|
||||
}
|
||||
}
|
||||
|
||||
// -- With-video-reference pricing --
|
||||
|
||||
mod video_reference_pricing {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn table_480p() {
|
||||
let expected = [36, 45, 53, 62, 71, 80, 89, 97, 106, 115, 124, 133];
|
||||
for (i, cents) in expected.iter().enumerate() {
|
||||
let duration = (i + 4) as u16;
|
||||
assert_eq!(
|
||||
cost_cents_with_video_ref(Some(RouterResolution::FourEightyP), duration, 1),
|
||||
*cents,
|
||||
"480p {duration}s",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_720p() {
|
||||
let expected = [89, 111, 133, 155, 177, 199, 221, 243, 265, 287, 309, 331];
|
||||
for (i, cents) in expected.iter().enumerate() {
|
||||
let duration = (i + 4) as u16;
|
||||
assert_eq!(
|
||||
cost_cents_with_video_ref(Some(RouterResolution::SevenTwentyP), duration, 1),
|
||||
*cents,
|
||||
"720p {duration}s",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_1080p() {
|
||||
let expected = [205, 256, 307, 358, 409, 460, 511, 563, 614, 665, 716, 767];
|
||||
for (i, cents) in expected.iter().enumerate() {
|
||||
let duration = (i + 4) as u16;
|
||||
assert_eq!(
|
||||
cost_cents_with_video_ref(Some(RouterResolution::TenEightyP), duration, 1),
|
||||
*cents,
|
||||
"1080p {duration}s",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_4k() {
|
||||
let expected = [456, 569, 683, 797, 911, 1025, 1138, 1252, 1366, 1480, 1594, 1707];
|
||||
for (i, cents) in expected.iter().enumerate() {
|
||||
let duration = (i + 4) as u16;
|
||||
assert_eq!(
|
||||
cost_cents_with_video_ref(Some(RouterResolution::FourK), duration, 1),
|
||||
*cents,
|
||||
"4K {duration}s",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Batches price identically to the equivalent total seconds.
|
||||
#[test]
|
||||
fn batch_multiplies_total_seconds() {
|
||||
assert_eq!(
|
||||
cost_cents_with_video_ref(Some(RouterResolution::FourEightyP), 5, 2),
|
||||
cost_cents_with_video_ref(Some(RouterResolution::FourEightyP), 10, 1),
|
||||
);
|
||||
assert_eq!(
|
||||
cost_cents_with_video_ref(Some(RouterResolution::FourK), 5, 2),
|
||||
cost_cents_with_video_ref(Some(RouterResolution::FourK), 10, 1),
|
||||
);
|
||||
}
|
||||
|
||||
/// Image and audio references do NOT trigger the with-reference pricing —
|
||||
/// only reference videos do. (The non-reference price is unchanged.)
|
||||
#[test]
|
||||
fn image_and_audio_references_price_as_no_reference() {
|
||||
use crate::api::audio_list_ref::AudioListRef;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
|
||||
let builder = GenerateVideoRequestBuilder {
|
||||
provider: RouterProvider::Artcraft,
|
||||
resolution: Some(RouterResolution::SevenTwentyP),
|
||||
duration_seconds: Some(5),
|
||||
video_batch_count: Some(1),
|
||||
reference_images: Some(ImageListRef::MediaFileTokens(vec![
|
||||
MediaFileToken::new("mf_img".to_string()),
|
||||
])),
|
||||
reference_audio: Some(AudioListRef::MediaFileTokens(vec![
|
||||
MediaFileToken::new("mf_audio".to_string()),
|
||||
])),
|
||||
..Default::default()
|
||||
};
|
||||
let cents = builder.build2()
|
||||
.expect("build2 should succeed")
|
||||
.estimate_cost()
|
||||
.expect("estimate_cost should succeed")
|
||||
.cost_in_usd_cents
|
||||
.unwrap();
|
||||
assert_eq!(cents, cost_cents(Some(RouterResolution::SevenTwentyP), 5, 1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,4 +440,28 @@ mod tests {
|
||||
.cost_in_usd_cents
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Like [`cost_cents`], with one reference video attached.
|
||||
fn cost_cents_with_video_ref(
|
||||
resolution: Option<RouterResolution>,
|
||||
duration_seconds: u16,
|
||||
video_batch_count: u16,
|
||||
) -> u64 {
|
||||
let builder = GenerateVideoRequestBuilder {
|
||||
provider: RouterProvider::Artcraft,
|
||||
resolution,
|
||||
duration_seconds: Some(duration_seconds),
|
||||
video_batch_count: Some(video_batch_count),
|
||||
reference_videos: Some(VideoListRef::MediaFileTokens(vec![
|
||||
MediaFileToken::new("mf_ref".to_string()),
|
||||
])),
|
||||
..Default::default()
|
||||
};
|
||||
builder.build2()
|
||||
.expect("build2 should succeed")
|
||||
.estimate_cost()
|
||||
.expect("estimate_cost should succeed")
|
||||
.cost_in_usd_cents
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
+141
-10
@@ -7,21 +7,22 @@ use crate::generate::generate_video::providers::artcraft::seedance_2p0_fast::req
|
||||
//
|
||||
// ArtCraft credits: 100 credits = $1.00. Credits always equal USD cents.
|
||||
//
|
||||
// The per-second USD cost varies by resolution. We derive cents from the
|
||||
// upstream credit rates and their credit-package prices, then set
|
||||
// ArtCraft credits = cents.
|
||||
// The per-second USD price varies by resolution.
|
||||
|
||||
/// USD cents per second by resolution, derived from upstream Fast rates:
|
||||
/// 480p: 10 upstream-credits/sec / 193 upstream-credits/$1 * 100 ~= 5.181 c/s
|
||||
/// (historical 193 credits/$1 derivation; the upstream package is now
|
||||
/// ~243 credits/$1 but user pricing is intentionally unchanged)
|
||||
/// 720p: 28 upstream-credits/sec / 220 upstream-credits/$1 * 100 ~= 12.727 c/s
|
||||
/// USD cents per second by resolution.
|
||||
///
|
||||
/// We keep these as f64 because per-second rates are fractional; rounding
|
||||
/// happens once at the end after multiplying by duration * batch.
|
||||
/// Kept as f64 because per-second rates are fractional; rounding happens
|
||||
/// once at the end after multiplying by duration * batch.
|
||||
const CENTS_PER_SECOND_480P: f64 = 5.181;
|
||||
const CENTS_PER_SECOND_720P: f64 = 12.727;
|
||||
|
||||
/// USD cents per second, in hundredths of a cent, when one or more
|
||||
/// reference videos are attached. Held as integer hundredths so the math is
|
||||
/// exact; rounded up to whole cents once, after multiplying by
|
||||
/// duration * batch.
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P: u64 = 841;
|
||||
const WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P: u64 = 1_560;
|
||||
|
||||
pub struct ArtcraftSeedance2p0FastCostState {
|
||||
pub resolution: CommonResolution,
|
||||
pub duration_seconds: u16,
|
||||
@@ -43,6 +44,19 @@ impl ArtcraftSeedance2p0FastCostState {
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
if self.has_video_reference {
|
||||
let usd_cents = self.with_video_reference_usd_cents();
|
||||
return VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(usd_cents),
|
||||
cost_in_usd_cents: Some(usd_cents),
|
||||
is_free: false,
|
||||
is_unlimited: false,
|
||||
is_rate_limited: false,
|
||||
has_watermark: false,
|
||||
failures_are_refunded: None,
|
||||
};
|
||||
}
|
||||
|
||||
let cents_per_second = match self.resolution {
|
||||
CommonResolution::FourEightyP => CENTS_PER_SECOND_480P,
|
||||
// Everything else (including 720p and unsupported resolutions) prices at 720p.
|
||||
@@ -62,13 +76,30 @@ impl ArtcraftSeedance2p0FastCostState {
|
||||
failures_are_refunded: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Price when one or more reference videos are attached.
|
||||
fn with_video_reference_usd_cents(&self) -> u64 {
|
||||
let centi_cents_per_second = match self.resolution {
|
||||
CommonResolution::FourEightyP => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_480P,
|
||||
// Everything else (including 720p and unsupported resolutions) prices at 720p.
|
||||
_ => WITH_VIDEO_REFERENCE_CENTI_CENTS_PER_SECOND_720P,
|
||||
};
|
||||
|
||||
let seconds = self.duration_seconds as u64 * self.batch_count as u64;
|
||||
|
||||
// Round up to whole cents.
|
||||
(centi_cents_per_second * seconds).div_ceil(100)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::api::router_resolution::RouterResolution;
|
||||
use crate::api::router_video_model::RouterVideoModel;
|
||||
use crate::api::router_provider::RouterProvider;
|
||||
use crate::api::video_list_ref::VideoListRef;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
|
||||
// -- 720p pricing --
|
||||
@@ -128,6 +159,81 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
// -- With-video-reference pricing --
|
||||
|
||||
mod video_reference_pricing {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn table_480p() {
|
||||
let expected = [34, 43, 51, 59, 68, 76, 85, 93, 101, 110, 118, 127];
|
||||
for (i, cents) in expected.iter().enumerate() {
|
||||
let duration = (i + 4) as u16;
|
||||
assert_eq!(
|
||||
cost_cents_with_video_ref(Some(RouterResolution::FourEightyP), duration, 1),
|
||||
*cents,
|
||||
"480p {duration}s",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_720p() {
|
||||
let expected = [63, 78, 94, 110, 125, 141, 156, 172, 188, 203, 219, 234];
|
||||
for (i, cents) in expected.iter().enumerate() {
|
||||
let duration = (i + 4) as u16;
|
||||
assert_eq!(
|
||||
cost_cents_with_video_ref(Some(RouterResolution::SevenTwentyP), duration, 1),
|
||||
*cents,
|
||||
"720p {duration}s",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Batches price identically to the equivalent total seconds.
|
||||
#[test]
|
||||
fn batch_multiplies_total_seconds() {
|
||||
assert_eq!(
|
||||
cost_cents_with_video_ref(Some(RouterResolution::FourEightyP), 5, 2),
|
||||
cost_cents_with_video_ref(Some(RouterResolution::FourEightyP), 10, 1),
|
||||
);
|
||||
assert_eq!(
|
||||
cost_cents_with_video_ref(Some(RouterResolution::SevenTwentyP), 5, 2),
|
||||
cost_cents_with_video_ref(Some(RouterResolution::SevenTwentyP), 10, 1),
|
||||
);
|
||||
}
|
||||
|
||||
/// Image and audio references do NOT trigger the with-reference pricing --
|
||||
/// only reference videos do. (The non-reference price is unchanged.)
|
||||
#[test]
|
||||
fn image_and_audio_references_price_as_no_reference() {
|
||||
use crate::api::audio_list_ref::AudioListRef;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
|
||||
let builder = GenerateVideoRequestBuilder {
|
||||
model: RouterVideoModel::Seedance2p0Fast,
|
||||
provider: RouterProvider::Artcraft,
|
||||
resolution: Some(RouterResolution::SevenTwentyP),
|
||||
duration_seconds: Some(5),
|
||||
video_batch_count: Some(1),
|
||||
reference_images: Some(ImageListRef::MediaFileTokens(vec![
|
||||
MediaFileToken::new("mf_img".to_string()),
|
||||
])),
|
||||
reference_audio: Some(AudioListRef::MediaFileTokens(vec![
|
||||
MediaFileToken::new("mf_audio".to_string()),
|
||||
])),
|
||||
..Default::default()
|
||||
};
|
||||
let cents = builder.build2()
|
||||
.expect("build2 should succeed")
|
||||
.estimate_cost()
|
||||
.expect("estimate_cost should succeed")
|
||||
.cost_in_usd_cents
|
||||
.unwrap();
|
||||
assert_eq!(cents, cost_cents(Some(RouterResolution::SevenTwentyP), 5, 1));
|
||||
}
|
||||
}
|
||||
|
||||
// -- Relative pricing --
|
||||
|
||||
mod relative_pricing_tests {
|
||||
@@ -216,4 +322,29 @@ mod tests {
|
||||
.cost_in_usd_cents
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Like [`cost_cents`], with one reference video attached.
|
||||
fn cost_cents_with_video_ref(
|
||||
resolution: Option<RouterResolution>,
|
||||
duration_seconds: u16,
|
||||
video_batch_count: u16,
|
||||
) -> u64 {
|
||||
let builder = GenerateVideoRequestBuilder {
|
||||
model: RouterVideoModel::Seedance2p0Fast,
|
||||
provider: RouterProvider::Artcraft,
|
||||
resolution,
|
||||
duration_seconds: Some(duration_seconds),
|
||||
video_batch_count: Some(video_batch_count),
|
||||
reference_videos: Some(VideoListRef::MediaFileTokens(vec![
|
||||
MediaFileToken::new("mf_ref".to_string()),
|
||||
])),
|
||||
..Default::default()
|
||||
};
|
||||
builder.build2()
|
||||
.expect("build2 should succeed")
|
||||
.estimate_cost()
|
||||
.expect("estimate_cost should succeed")
|
||||
.cost_in_usd_cents
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
+12
-12
@@ -55,12 +55,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn full_duration_table() {
|
||||
assert_eq!(cents(Some(RouterResolution::FourEightyP), 4), 82);
|
||||
assert_eq!(cents(Some(RouterResolution::FourEightyP), 10), 205);
|
||||
assert_eq!(cents(Some(RouterResolution::FourEightyP), 15), 307);
|
||||
assert_eq!(cents(Some(RouterResolution::FourEightyP), 20), 409);
|
||||
assert_eq!(cents(Some(RouterResolution::FourEightyP), 25), 511);
|
||||
assert_eq!(cents(Some(RouterResolution::FourEightyP), 30), 613);
|
||||
assert_eq!(cents(Some(RouterResolution::FourEightyP), 4), 86);
|
||||
assert_eq!(cents(Some(RouterResolution::FourEightyP), 10), 214);
|
||||
assert_eq!(cents(Some(RouterResolution::FourEightyP), 15), 321);
|
||||
assert_eq!(cents(Some(RouterResolution::FourEightyP), 20), 428);
|
||||
assert_eq!(cents(Some(RouterResolution::FourEightyP), 25), 535);
|
||||
assert_eq!(cents(Some(RouterResolution::FourEightyP), 30), 642);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,12 +69,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn full_duration_table() {
|
||||
assert_eq!(cents(Some(RouterResolution::SevenTwentyP), 4), 164);
|
||||
assert_eq!(cents(Some(RouterResolution::SevenTwentyP), 10), 409);
|
||||
assert_eq!(cents(Some(RouterResolution::SevenTwentyP), 15), 613);
|
||||
assert_eq!(cents(Some(RouterResolution::SevenTwentyP), 20), 817);
|
||||
assert_eq!(cents(Some(RouterResolution::SevenTwentyP), 25), 1021);
|
||||
assert_eq!(cents(Some(RouterResolution::SevenTwentyP), 30), 1226);
|
||||
assert_eq!(cents(Some(RouterResolution::SevenTwentyP), 4), 172);
|
||||
assert_eq!(cents(Some(RouterResolution::SevenTwentyP), 10), 428);
|
||||
assert_eq!(cents(Some(RouterResolution::SevenTwentyP), 15), 642);
|
||||
assert_eq!(cents(Some(RouterResolution::SevenTwentyP), 20), 856);
|
||||
assert_eq!(cents(Some(RouterResolution::SevenTwentyP), 25), 1070);
|
||||
assert_eq!(cents(Some(RouterResolution::SevenTwentyP), 30), 1283);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+2
-2
@@ -119,9 +119,9 @@ pub fn seedance_2p0_byteplus_mini_usd_cents(
|
||||
// change the price.
|
||||
|
||||
/// Seedance 2.5 Preview — 480p price, USD cents per second.
|
||||
const SEEDANCE_2P5_PREVIEW_CENTS_PER_SECOND_480P: f64 = 20.41909465;
|
||||
const SEEDANCE_2P5_PREVIEW_CENTS_PER_SECOND_480P: f64 = 21.38234568;
|
||||
/// Seedance 2.5 Preview — 720p price, USD cents per second.
|
||||
const SEEDANCE_2P5_PREVIEW_CENTS_PER_SECOND_720P: f64 = 40.8381893;
|
||||
const SEEDANCE_2P5_PREVIEW_CENTS_PER_SECOND_720P: f64 = 42.76469136;
|
||||
|
||||
/// ArtCraft's price (USD cents) for Seedance 2.5 Preview.
|
||||
///
|
||||
|
||||
+7
-6
@@ -15,8 +15,9 @@ impl ArtcraftSora2CostState {
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// Sora 2: $0.10/second. Default duration 4s.
|
||||
let cost_in_usd_cents = self.duration_seconds * 10;
|
||||
// Sora 2: 11.5¢/second (held in hundredths of a cent, rounded up to
|
||||
// whole cents). Default duration 4s.
|
||||
let cost_in_usd_cents = (self.duration_seconds * 1_150).div_ceil(100);
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_usd_cents),
|
||||
@@ -48,14 +49,14 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn four_seconds_is_40() { assert_eq!(cost_cents(Some(4)), 40); }
|
||||
fn four_seconds_is_46() { assert_eq!(cost_cents(Some(4)), 46); }
|
||||
|
||||
#[test]
|
||||
fn eight_seconds_is_80() { assert_eq!(cost_cents(Some(8)), 80); }
|
||||
fn eight_seconds_is_92() { assert_eq!(cost_cents(Some(8)), 92); }
|
||||
|
||||
#[test]
|
||||
fn twelve_seconds_is_120() { assert_eq!(cost_cents(Some(12)), 120); }
|
||||
fn twelve_seconds_is_138() { assert_eq!(cost_cents(Some(12)), 138); }
|
||||
|
||||
#[test]
|
||||
fn default_duration_is_4s_priced_at_40() { assert_eq!(cost_cents(None), 40); }
|
||||
fn default_duration_is_4s_priced_at_46() { assert_eq!(cost_cents(None), 46); }
|
||||
}
|
||||
|
||||
+13
-12
@@ -19,9 +19,10 @@ impl ArtcraftSora2ProCostState {
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// Sora 2 Pro: $0.30/sec @ 720p, $0.50/sec @ 1080p. Default 4s.
|
||||
let per_second_cents: u64 = if self.is_ten_eighty_p { 50 } else { 30 };
|
||||
let cost_in_usd_cents = per_second_cents * self.duration_seconds;
|
||||
// Sora 2 Pro: 34.5¢/sec @ 720p, 57.5¢/sec @ 1080p (held in hundredths
|
||||
// of a cent, rounded up to whole cents). Default 4s.
|
||||
let per_second_centi_cents: u64 = if self.is_ten_eighty_p { 5_750 } else { 3_450 };
|
||||
let cost_in_usd_cents = (per_second_centi_cents * self.duration_seconds).div_ceil(100);
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_usd_cents),
|
||||
@@ -77,30 +78,30 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn p720_4s_is_120() { assert_eq!(cost_cents(Some(4), Some(RouterResolution::SevenTwentyP), false), 120); }
|
||||
fn p720_4s_is_138() { assert_eq!(cost_cents(Some(4), Some(RouterResolution::SevenTwentyP), false), 138); }
|
||||
|
||||
#[test]
|
||||
fn p720_8s_is_240() { assert_eq!(cost_cents(Some(8), Some(RouterResolution::SevenTwentyP), false), 240); }
|
||||
fn p720_8s_is_276() { assert_eq!(cost_cents(Some(8), Some(RouterResolution::SevenTwentyP), false), 276); }
|
||||
|
||||
#[test]
|
||||
fn p720_12s_is_360() { assert_eq!(cost_cents(Some(12), Some(RouterResolution::SevenTwentyP), false), 360); }
|
||||
fn p720_12s_is_414() { assert_eq!(cost_cents(Some(12), Some(RouterResolution::SevenTwentyP), false), 414); }
|
||||
|
||||
#[test]
|
||||
fn p1080_4s_is_200() { assert_eq!(cost_cents(Some(4), Some(RouterResolution::TenEightyP), false), 200); }
|
||||
fn p1080_4s_is_230() { assert_eq!(cost_cents(Some(4), Some(RouterResolution::TenEightyP), false), 230); }
|
||||
|
||||
#[test]
|
||||
fn p1080_12s_is_600() { assert_eq!(cost_cents(Some(12), Some(RouterResolution::TenEightyP), false), 600); }
|
||||
fn p1080_12s_is_690() { assert_eq!(cost_cents(Some(12), Some(RouterResolution::TenEightyP), false), 690); }
|
||||
|
||||
#[test]
|
||||
fn t2v_default_resolution_priced_as_1080p() {
|
||||
// T2V (no start_frame) with no resolution → 1080p default → 200¢ at 4s.
|
||||
assert_eq!(cost_cents(Some(4), None, false), 200);
|
||||
// T2V (no start_frame) with no resolution → 1080p default → 230¢ at 4s.
|
||||
assert_eq!(cost_cents(Some(4), None, false), 230);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn i2v_default_resolution_priced_as_720p() {
|
||||
// I2V (with start_frame) with no resolution → 720p default → 120¢ at 4s.
|
||||
assert_eq!(cost_cents(Some(4), None, true), 120);
|
||||
// I2V (with start_frame) with no resolution → 720p default → 138¢ at 4s.
|
||||
assert_eq!(cost_cents(Some(4), None, true), 138);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-15
@@ -4,10 +4,10 @@ use crate::generate::generate_video::video_generation_cost_estimate::VideoGenera
|
||||
use crate::generate::generate_video::providers::artcraft::veo_3p1_lite::request::ArtcraftVeo3p1LiteRequestState;
|
||||
|
||||
/// Per-second rates in hundredths of a US cent.
|
||||
const SEVEN_TWENTY_P_AUDIO_OFF_RATE: u64 = 315;
|
||||
const SEVEN_TWENTY_P_AUDIO_ON_RATE: u64 = 525;
|
||||
const TEN_EIGHTY_P_AUDIO_OFF_RATE: u64 = 525;
|
||||
const TEN_EIGHTY_P_AUDIO_ON_RATE: u64 = 840;
|
||||
const SEVEN_TWENTY_P_AUDIO_OFF_RATE: u64 = 345;
|
||||
const SEVEN_TWENTY_P_AUDIO_ON_RATE: u64 = 575;
|
||||
const TEN_EIGHTY_P_AUDIO_OFF_RATE: u64 = 575;
|
||||
const TEN_EIGHTY_P_AUDIO_ON_RATE: u64 = 920;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ArtcraftVeo3p1LiteCostState {
|
||||
@@ -88,28 +88,28 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn defaults_8s_720p_audio_on_is_42() { assert_eq!(cost_cents(None, None, None), 42); }
|
||||
fn defaults_8s_720p_audio_on_is_46() { assert_eq!(cost_cents(None, None, None), 46); }
|
||||
|
||||
#[test]
|
||||
fn audio_off_8s_720p_is_26() {
|
||||
// 315 * 8 = 2520 hundredth-cents → 26 cents.
|
||||
assert_eq!(cost_cents(Some(8), None, Some(false)), 26);
|
||||
fn audio_off_8s_720p_is_28() {
|
||||
// 345 * 8 = 2760 hundredth-cents → 28 cents.
|
||||
assert_eq!(cost_cents(Some(8), None, Some(false)), 28);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_on_8s_1080p_is_68() {
|
||||
// 840 * 8 = 6720 hundredth-cents → 68 cents.
|
||||
assert_eq!(cost_cents(Some(8), Some(RouterResolution::TenEightyP), Some(true)), 68);
|
||||
fn audio_on_8s_1080p_is_74() {
|
||||
// 920 * 8 = 7360 hundredth-cents → 74 cents.
|
||||
assert_eq!(cost_cents(Some(8), Some(RouterResolution::TenEightyP), Some(true)), 74);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_off_8s_1080p_is_42() {
|
||||
assert_eq!(cost_cents(Some(8), Some(RouterResolution::TenEightyP), Some(false)), 42);
|
||||
fn audio_off_8s_1080p_is_46() {
|
||||
assert_eq!(cost_cents(Some(8), Some(RouterResolution::TenEightyP), Some(false)), 46);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audio_on_4s_720p_is_21() {
|
||||
assert_eq!(cost_cents(Some(4), None, Some(true)), 21);
|
||||
fn audio_on_4s_720p_is_23() {
|
||||
assert_eq!(cost_cents(Some(4), None, Some(true)), 23);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+3
@@ -16,6 +16,9 @@ pub struct ApiOrWebSessionUserRecord {
|
||||
pub is_banned: bool,
|
||||
pub can_ban_users: bool,
|
||||
|
||||
/// Optional comma-separated list of parseable `UserFeatureFlag` enum features.
|
||||
pub maybe_feature_flags: Option<String>,
|
||||
|
||||
/// Only set when the lookup was by API key.
|
||||
pub maybe_api_key_token: Option<ApiKeyToken>,
|
||||
}
|
||||
|
||||
+2
@@ -40,6 +40,7 @@ SELECT
|
||||
users.user_role_slug,
|
||||
users.is_banned,
|
||||
user_roles.can_ban_users,
|
||||
users.maybe_feature_flags,
|
||||
api_keys.token as `api_key_token: ApiKeyToken`
|
||||
FROM api_keys
|
||||
INNER JOIN users
|
||||
@@ -65,6 +66,7 @@ LIMIT 1
|
||||
user_role_slug: r.user_role_slug,
|
||||
is_banned: i8_to_bool(r.is_banned),
|
||||
can_ban_users: nullable_i8_to_bool_default_false(r.can_ban_users),
|
||||
maybe_feature_flags: r.maybe_feature_flags,
|
||||
maybe_api_key_token: Some(r.api_key_token),
|
||||
}))
|
||||
}
|
||||
|
||||
+3
-1
@@ -37,7 +37,8 @@ SELECT
|
||||
users.email_address,
|
||||
users.user_role_slug,
|
||||
users.is_banned,
|
||||
user_roles.can_ban_users
|
||||
user_roles.can_ban_users,
|
||||
users.maybe_feature_flags
|
||||
FROM user_sessions
|
||||
INNER JOIN users
|
||||
ON users.token = user_sessions.user_token
|
||||
@@ -62,6 +63,7 @@ LIMIT 1
|
||||
user_role_slug: r.user_role_slug,
|
||||
is_banned: i8_to_bool(r.is_banned),
|
||||
can_ban_users: nullable_i8_to_bool_default_false(r.can_ban_users),
|
||||
maybe_feature_flags: r.maybe_feature_flags,
|
||||
maybe_api_key_token: None,
|
||||
}))
|
||||
}
|
||||
|
||||
+16
-17
@@ -16,6 +16,7 @@ use enums::by_table::prompts::prompt_type::PromptType;
|
||||
use enums::common::generation::common_generation_mode::CommonGenerationMode;
|
||||
use enums::common::generation::common_model_type::CommonModelType;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::platform_type::PlatformType;
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use mysql_queries::queries::debug_logs::insert_debug_log::{insert_debug_log, InsertDebugLogArgs};
|
||||
use mysql_queries::queries::generic_inference::api_providers::kinovi_web::insert_generic_inference_job_for_kinovi_web_queue_with_apriori_job_token::KinoviVersion;
|
||||
@@ -38,12 +39,14 @@ use crate::http_server::endpoints::omni_gen::generate::audio::pipeline_v2::run_p
|
||||
use crate::http_server::endpoints::omni_gen::generate::video::insert_db_job::shared_job_args::SharedJobArgs;
|
||||
use crate::http_server::endpoints::omni_gen::shared_utils::kinovi_account::KinoviAccount;
|
||||
use crate::http_server::endpoints::omni_gen::shared_utils::audio::validate_audio_request::validate_audio_request;
|
||||
use crate::http_server::user_lookup::api_or_web_session::require_api_or_web_session::{require_api_or_web_session, SessionType};
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use crate::http_server::web_utils::get_request_platform_type::get_request_platform_type;
|
||||
use crate::state::server_state::ServerState;
|
||||
use crate::util::lookup::lookup_media_files_as_cdn_url_list_and_map::lookup_media_files_as_cdn_url_list_and_map;
|
||||
|
||||
/// Generate audio using the omni-gen unified endpoint.
|
||||
/// Authenticates as either a web-session (cookie) user or an API-key (`Authorization` header) user.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
tag = "Omni Gen",
|
||||
@@ -81,25 +84,18 @@ pub async fn omni_gen_audio_generate_handler(
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool.acquire().await?;
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
CommonWebError::from(e)
|
||||
})?;
|
||||
|
||||
let session = match maybe_user_session.as_ref() {
|
||||
Some(session) => session,
|
||||
None => return Err(CommonWebError::NotAuthorized),
|
||||
};
|
||||
// Either an API-key user (Authorization header) or a web-session (cookie) user.
|
||||
let session = require_api_or_web_session(
|
||||
&http_request,
|
||||
&server_state.session_checker,
|
||||
&server_state.avt_cookie_manager,
|
||||
&mut *mysql_connection,
|
||||
).await?;
|
||||
|
||||
let user_token = &session.user_token;
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
.get_avt_token_from_request(&http_request);
|
||||
// AVT tokens are web-session only; API-key sessions never carry one.
|
||||
let maybe_avt_token = session.maybe_avt_token.clone();
|
||||
|
||||
// ==================== IDEMPOTENCY ==================== //
|
||||
|
||||
@@ -224,7 +220,10 @@ pub async fn omni_gen_audio_generate_handler(
|
||||
|
||||
// ==================== WRITE RESULT ==================== //
|
||||
|
||||
let maybe_platform_type = get_request_platform_type(&http_request);
|
||||
let maybe_platform_type = match session.session_type {
|
||||
SessionType::Api => Some(PlatformType::ApiKey),
|
||||
SessionType::WebSession => get_request_platform_type(&http_request),
|
||||
};
|
||||
|
||||
let mut transaction = mysql_connection.begin().await.map_err(|err| {
|
||||
error!("Error starting MySQL transaction: {:?}", err);
|
||||
|
||||
+16
-17
@@ -15,6 +15,7 @@ use enums::common::generation::common_generation_mode::CommonGenerationMode;
|
||||
use enums::common::generation::common_image_model::CommonImageModel;
|
||||
use enums::common::generation::common_model_type::CommonModelType;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::platform_type::PlatformType;
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use enums::by_table::debug_logs::debug_log_level::DebugLogLevel;
|
||||
use mysql_queries::queries::debug_logs::insert_debug_log::{insert_debug_log, InsertDebugLogArgs};
|
||||
@@ -36,6 +37,7 @@ use crate::http_server::endpoints::omni_gen::generate::image::insert_db_job::ins
|
||||
use crate::http_server::endpoints::omni_gen::generate::image::insert_db_job::shared_job_args::SharedJobArgs;
|
||||
use crate::http_server::endpoints::omni_gen::generate::image::pipeline_v2::run_pipeline_v2::{run_pipeline_v2, RunPipelineV2Args};
|
||||
use crate::http_server::endpoints::omni_gen::shared_utils::kinovi_account::KinoviAccount;
|
||||
use crate::http_server::user_lookup::api_or_web_session::require_api_or_web_session::{require_api_or_web_session, SessionType};
|
||||
use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags;
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use crate::http_server::web_utils::get_request_platform_type::get_request_platform_type;
|
||||
@@ -43,6 +45,7 @@ use crate::state::server_state::ServerState;
|
||||
use crate::util::lookup::lookup_media_files_as_cdn_url_list_and_map::lookup_media_files_as_cdn_url_list_and_map;
|
||||
|
||||
/// Generate an image using the omni-gen unified endpoint.
|
||||
/// Authenticates as either a web-session (cookie) user or an API-key (`Authorization` header) user.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
tag = "Omni Gen",
|
||||
@@ -76,25 +79,18 @@ pub async fn omni_gen_image_generate_handler(
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool.acquire().await?;
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
CommonWebError::from(e)
|
||||
})?;
|
||||
|
||||
let session = match maybe_user_session.as_ref() {
|
||||
Some(session) => session,
|
||||
None => return Err(CommonWebError::NotAuthorized),
|
||||
};
|
||||
// Either an API-key user (Authorization header) or a web-session (cookie) user.
|
||||
let session = require_api_or_web_session(
|
||||
&http_request,
|
||||
&server_state.session_checker,
|
||||
&server_state.avt_cookie_manager,
|
||||
&mut *mysql_connection,
|
||||
).await?;
|
||||
|
||||
let user_token = &session.user_token;
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
.get_avt_token_from_request(&http_request);
|
||||
// AVT tokens are web-session only; API-key sessions never carry one.
|
||||
let maybe_avt_token = session.maybe_avt_token.clone();
|
||||
|
||||
// ==================== MODEL ACCESS CHECK ==================== //
|
||||
|
||||
@@ -215,7 +211,10 @@ pub async fn omni_gen_image_generate_handler(
|
||||
|
||||
// ==================== WRITE RESULT ==================== //
|
||||
|
||||
let maybe_platform_type = get_request_platform_type(&http_request);
|
||||
let maybe_platform_type = match session.session_type {
|
||||
SessionType::Api => Some(PlatformType::ApiKey),
|
||||
SessionType::WebSession => get_request_platform_type(&http_request),
|
||||
};
|
||||
|
||||
let mut transaction = mysql_connection
|
||||
.begin()
|
||||
|
||||
+16
-17
@@ -16,6 +16,7 @@ use enums::by_table::prompts::prompt_type::PromptType;
|
||||
use enums::common::generation::common_generation_mode::CommonGenerationMode;
|
||||
use enums::common::generation::common_model_type::CommonModelType;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::platform_type::PlatformType;
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use mysql_queries::queries::debug_logs::insert_debug_log::{insert_debug_log, InsertDebugLogArgs};
|
||||
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
|
||||
@@ -35,12 +36,14 @@ use crate::http_server::endpoints::omni_gen::generate::mesh::insert_db_job::inse
|
||||
use crate::http_server::endpoints::omni_gen::generate::mesh::pipeline_v2::run_pipeline_v2::{run_pipeline_v2, RunPipelineV2Args};
|
||||
use crate::http_server::endpoints::omni_gen::generate::video::insert_db_job::shared_job_args::SharedJobArgs;
|
||||
use crate::http_server::endpoints::omni_gen::shared_utils::mesh::validate_mesh_request::validate_mesh_request;
|
||||
use crate::http_server::user_lookup::api_or_web_session::require_api_or_web_session::{require_api_or_web_session, SessionType};
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use crate::http_server::web_utils::get_request_platform_type::get_request_platform_type;
|
||||
use crate::state::server_state::ServerState;
|
||||
use crate::util::lookup::lookup_media_files_as_cdn_url_list_and_map::lookup_media_files_as_cdn_url_list_and_map;
|
||||
|
||||
/// Generate a mesh (3D object) using the omni-gen unified endpoint.
|
||||
/// Authenticates as either a web-session (cookie) user or an API-key (`Authorization` header) user.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
tag = "Omni Gen",
|
||||
@@ -78,25 +81,18 @@ pub async fn omni_gen_mesh_generate_handler(
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool.acquire().await?;
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
CommonWebError::from(e)
|
||||
})?;
|
||||
|
||||
let session = match maybe_user_session.as_ref() {
|
||||
Some(session) => session,
|
||||
None => return Err(CommonWebError::NotAuthorized),
|
||||
};
|
||||
// Either an API-key user (Authorization header) or a web-session (cookie) user.
|
||||
let session = require_api_or_web_session(
|
||||
&http_request,
|
||||
&server_state.session_checker,
|
||||
&server_state.avt_cookie_manager,
|
||||
&mut *mysql_connection,
|
||||
).await?;
|
||||
|
||||
let user_token = &session.user_token;
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
.get_avt_token_from_request(&http_request);
|
||||
// AVT tokens are web-session only; API-key sessions never carry one.
|
||||
let maybe_avt_token = session.maybe_avt_token.clone();
|
||||
|
||||
// ==================== IDEMPOTENCY ==================== //
|
||||
|
||||
@@ -211,7 +207,10 @@ pub async fn omni_gen_mesh_generate_handler(
|
||||
|
||||
// ==================== WRITE RESULT ==================== //
|
||||
|
||||
let maybe_platform_type = get_request_platform_type(&http_request);
|
||||
let maybe_platform_type = match session.session_type {
|
||||
SessionType::Api => Some(PlatformType::ApiKey),
|
||||
SessionType::WebSession => get_request_platform_type(&http_request),
|
||||
};
|
||||
|
||||
let mut transaction = mysql_connection.begin().await.map_err(|err| {
|
||||
error!("Error starting MySQL transaction: {:?}", err);
|
||||
|
||||
+16
-17
@@ -16,6 +16,7 @@ use enums::by_table::prompts::prompt_type::PromptType;
|
||||
use enums::common::generation::common_generation_mode::CommonGenerationMode;
|
||||
use enums::common::generation::common_model_type::CommonModelType;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::platform_type::PlatformType;
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use mysql_queries::queries::debug_logs::insert_debug_log::{insert_debug_log, InsertDebugLogArgs};
|
||||
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
|
||||
@@ -36,12 +37,14 @@ use crate::http_server::endpoints::omni_gen::generate::splat::insert_db_job::ins
|
||||
use crate::http_server::endpoints::omni_gen::generate::splat::pipeline_v2::run_pipeline_v2::{run_pipeline_v2, RunPipelineV2Args};
|
||||
use crate::http_server::endpoints::omni_gen::generate::video::insert_db_job::shared_job_args::SharedJobArgs;
|
||||
use crate::http_server::endpoints::omni_gen::shared_utils::splat::validate_splat_request::validate_splat_request;
|
||||
use crate::http_server::user_lookup::api_or_web_session::require_api_or_web_session::{require_api_or_web_session, SessionType};
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use crate::http_server::web_utils::get_request_platform_type::get_request_platform_type;
|
||||
use crate::state::server_state::ServerState;
|
||||
use crate::util::lookup::lookup_media_files_as_cdn_url_list_and_map::lookup_media_files_as_cdn_url_list_and_map;
|
||||
|
||||
/// Generate a splat (3D world) using the omni-gen unified endpoint.
|
||||
/// Authenticates as either a web-session (cookie) user or an API-key (`Authorization` header) user.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
tag = "Omni Gen",
|
||||
@@ -79,25 +82,18 @@ pub async fn omni_gen_splat_generate_handler(
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool.acquire().await?;
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
CommonWebError::from(e)
|
||||
})?;
|
||||
|
||||
let session = match maybe_user_session.as_ref() {
|
||||
Some(session) => session,
|
||||
None => return Err(CommonWebError::NotAuthorized),
|
||||
};
|
||||
// Either an API-key user (Authorization header) or a web-session (cookie) user.
|
||||
let session = require_api_or_web_session(
|
||||
&http_request,
|
||||
&server_state.session_checker,
|
||||
&server_state.avt_cookie_manager,
|
||||
&mut *mysql_connection,
|
||||
).await?;
|
||||
|
||||
let user_token = &session.user_token;
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
.get_avt_token_from_request(&http_request);
|
||||
// AVT tokens are web-session only; API-key sessions never carry one.
|
||||
let maybe_avt_token = session.maybe_avt_token.clone();
|
||||
|
||||
// ==================== IDEMPOTENCY ==================== //
|
||||
|
||||
@@ -216,7 +212,10 @@ pub async fn omni_gen_splat_generate_handler(
|
||||
|
||||
// ==================== WRITE RESULT ==================== //
|
||||
|
||||
let maybe_platform_type = get_request_platform_type(&http_request);
|
||||
let maybe_platform_type = match session.session_type {
|
||||
SessionType::Api => Some(PlatformType::ApiKey),
|
||||
SessionType::WebSession => get_request_platform_type(&http_request),
|
||||
};
|
||||
|
||||
let mut transaction = mysql_connection.begin().await.map_err(|err| {
|
||||
error!("Error starting MySQL transaction: {:?}", err);
|
||||
|
||||
+16
-17
@@ -16,6 +16,7 @@ use enums::common::generation::common_generation_mode::CommonGenerationMode;
|
||||
use enums::common::generation::common_model_type::CommonModelType;
|
||||
use enums::common::generation::common_video_model::CommonVideoModel;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::platform_type::PlatformType;
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use enums::by_table::debug_logs::debug_log_level::DebugLogLevel;
|
||||
use mysql_queries::queries::debug_logs::insert_debug_log::{insert_debug_log, InsertDebugLogArgs};
|
||||
@@ -43,6 +44,7 @@ use crate::http_server::endpoints::omni_gen::generate::video::insert_db_job::sha
|
||||
use crate::http_server::endpoints::omni_gen::shared_utils::kinovi_account::KinoviAccount;
|
||||
use crate::http_server::endpoints::omni_gen::generate::video::pipeline_v2::run_pipeline_v2::{run_pipeline_v2, RunPipelineV2Args};
|
||||
use crate::http_server::endpoints::omni_gen::shared_utils::video::validate_video_request::validate_video_request;
|
||||
use crate::http_server::user_lookup::api_or_web_session::require_api_or_web_session::{require_api_or_web_session, SessionType};
|
||||
use crate::http_server::user_lookup::user_session::session_utils::lookup::user_session_feature_flags::UserSessionFeatureFlags;
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use crate::http_server::web_utils::get_request_platform_type::get_request_platform_type;
|
||||
@@ -50,6 +52,7 @@ use crate::state::server_state::ServerState;
|
||||
use crate::util::lookup::lookup_media_files_as_cdn_url_list_and_map::lookup_media_files_as_cdn_url_list_and_map;
|
||||
|
||||
/// Generate a video using the omni-gen unified endpoint.
|
||||
/// Authenticates as either a web-session (cookie) user or an API-key (`Authorization` header) user.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
tag = "Omni Gen",
|
||||
@@ -87,25 +90,18 @@ pub async fn omni_gen_video_generate_handler(
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool.acquire().await?;
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
CommonWebError::from(e)
|
||||
})?;
|
||||
|
||||
let session = match maybe_user_session.as_ref() {
|
||||
Some(session) => session,
|
||||
None => return Err(CommonWebError::NotAuthorized),
|
||||
};
|
||||
// Either an API-key user (Authorization header) or a web-session (cookie) user.
|
||||
let session = require_api_or_web_session(
|
||||
&http_request,
|
||||
&server_state.session_checker,
|
||||
&server_state.avt_cookie_manager,
|
||||
&mut *mysql_connection,
|
||||
).await?;
|
||||
|
||||
let user_token = &session.user_token;
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
.get_avt_token_from_request(&http_request);
|
||||
// AVT tokens are web-session only; API-key sessions never carry one.
|
||||
let maybe_avt_token = session.maybe_avt_token.clone();
|
||||
|
||||
// ==================== MODEL ACCESS CHECK ==================== //
|
||||
|
||||
@@ -265,7 +261,10 @@ pub async fn omni_gen_video_generate_handler(
|
||||
|
||||
// ==================== WRITE RESULT ==================== //
|
||||
|
||||
let maybe_platform_type = get_request_platform_type(&http_request);
|
||||
let maybe_platform_type = match session.session_type {
|
||||
SessionType::Api => Some(PlatformType::ApiKey),
|
||||
SessionType::WebSession => get_request_platform_type(&http_request),
|
||||
};
|
||||
|
||||
let mut transaction = mysql_connection.begin().await.map_err(|err| {
|
||||
error!("Error starting MySQL transaction: {:?}", err);
|
||||
|
||||
+4
@@ -37,6 +37,9 @@ pub struct ApiOrWebSession {
|
||||
pub user_role_slug: String,
|
||||
pub can_ban_users: bool,
|
||||
|
||||
/// Optional comma-separated list of parseable `UserFeatureFlag` enum features.
|
||||
pub maybe_feature_flags: Option<String>,
|
||||
|
||||
/// Only present for API-key sessions.
|
||||
pub maybe_api_key_token: Option<ApiKeyToken>,
|
||||
|
||||
@@ -184,6 +187,7 @@ fn into_session_rejecting_banned(
|
||||
email_address: record.email_address,
|
||||
user_role_slug: record.user_role_slug,
|
||||
can_ban_users: record.can_ban_users,
|
||||
maybe_feature_flags: record.maybe_feature_flags,
|
||||
maybe_api_key_token: record.maybe_api_key_token,
|
||||
maybe_avt_token,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user