mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-13 18:24:28 -05:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8672af848c | ||
|
|
361632da6d | ||
|
|
45ce366a8f | ||
|
|
48929d9ccd | ||
|
|
e4c20ce7e8 |
+1
-1
@@ -120,7 +120,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 3
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+2
-2
@@ -60,7 +60,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -70,7 +70,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+2
-2
@@ -210,7 +210,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -220,7 +220,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 3
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -180,7 +180,7 @@
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+2
-2
@@ -40,7 +40,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -50,7 +50,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+2
-2
@@ -50,7 +50,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -60,7 +60,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+2
-2
@@ -210,7 +210,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -220,7 +220,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+2
-2
@@ -40,7 +40,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -50,7 +50,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -180,7 +180,7 @@
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 3
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -190,7 +190,7 @@
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 3
|
||||
"max_size": 11
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO generic_inference_jobs\nSET\n token = ?,\n uuid_idempotency_token = ?,\n\n job_type = ?,\n\n maybe_external_third_party = ?,\n maybe_external_third_party_id = ?,\n\n product_category = ?,\n inference_category = ?,\n\n maybe_model_type = ?,\n maybe_model_token = ?,\n\n maybe_input_source_token = ?,\n maybe_input_source_token_type = ?,\n\n maybe_download_url = ?,\n maybe_cover_image_media_file_token = ?,\n\n maybe_prompt_token = ?,\n maybe_wallet_ledger_entry_token = ?,\n\n maybe_raw_inference_text = ?,\n\n maybe_inference_args = ?,\n\n maybe_creator_user_token = ?,\n maybe_creator_anonymous_visitor_token = ?,\n creator_ip_address = ?,\n creator_set_visibility = ?,\n\n platform_type = ?,\n\n priority_level = ?,\n is_keepalive_required = ?,\n max_duration_seconds = ?,\n is_debug_request = ?,\n\n maybe_routing_tag = ?,\n\n maybe_debug_log_event_token = ?,\n\n frontend_failure_category = ?,\n failure_reason = ?,\n\n status = ?\n ",
|
||||
"query": "\nINSERT INTO generic_inference_jobs\nSET\n token = ?,\n uuid_idempotency_token = ?,\n\n job_type = ?,\n\n maybe_external_third_party = ?,\n maybe_external_third_party_id = ?,\n\n product_category = ?,\n inference_category = ?,\n\n maybe_model_type = ?,\n maybe_model_token = ?,\n\n maybe_input_source_token = ?,\n maybe_input_source_token_type = ?,\n\n maybe_download_url = ?,\n maybe_cover_image_media_file_token = ?,\n\n maybe_prompt_token = ?,\n maybe_wallet_ledger_entry_token = ?,\n\n maybe_raw_inference_text = ?,\n\n maybe_inference_args = ?,\n\n maybe_creator_user_token = ?,\n maybe_creator_anonymous_visitor_token = ?,\n creator_ip_address = ?,\n creator_set_visibility = ?,\n\n platform_type = ?,\n\n maybe_external_third_party_cost_credits = ?,\n maybe_external_third_party_cost_usd_cents = ?,\n maybe_system_cost_credits = ?,\n maybe_system_cost_usd_cents = ?,\n\n priority_level = ?,\n is_keepalive_required = ?,\n max_duration_seconds = ?,\n is_debug_request = ?,\n\n maybe_routing_tag = ?,\n\n maybe_debug_log_event_token = ?,\n\n frontend_failure_category = ?,\n failure_reason = ?,\n\n status = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 31
|
||||
"Right": 35
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "108731dc51d915ebb8b6ed391c5bae4c41ae6f2c813be92c07d8a10ca401db53"
|
||||
"hash": "5c67c3dd7f8bb6771c6b058a8cf6ffcc66b2be2bea41bd988070713173982dab"
|
||||
}
|
||||
+1
-1
@@ -160,7 +160,7 @@
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+2
-2
@@ -60,7 +60,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -70,7 +70,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -150,7 +150,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 3
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -160,7 +160,7 @@
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+3
-3
@@ -190,7 +190,7 @@
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -200,7 +200,7 @@
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 5
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -210,7 +210,7 @@
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 5
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -160,7 +160,7 @@
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+2
-2
@@ -50,7 +50,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -60,7 +60,7 @@
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -140,7 +140,7 @@
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -150,7 +150,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 3
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 3
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -160,7 +160,7 @@
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -270,7 +270,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -160,7 +160,7 @@
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+3
-3
@@ -190,7 +190,7 @@
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -200,7 +200,7 @@
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 5
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -210,7 +210,7 @@
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 5
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -150,7 +150,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 3
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+2
-2
@@ -180,7 +180,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -190,7 +190,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -140,7 +140,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+2
-2
@@ -180,7 +180,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -190,7 +190,7 @@
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -190,7 +190,7 @@
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+1
-1
@@ -140,7 +140,7 @@
|
||||
"type": "Long",
|
||||
"flags": "",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
+27
-3
@@ -141,21 +141,45 @@ mod tests {
|
||||
fn batch_1_matches_kinovi() {
|
||||
let kinovi = KinoviMidjourney7CostState { batch_count: KinoviMidjourneyBatchCount::One }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost_cents(1), kinovi.cost_in_usd_cents.unwrap());
|
||||
// NB: The kinovi provider estimate rounds fractional cents UP
|
||||
// (KinoviGenerationCost) while this artcraft user price rounds to
|
||||
// nearest — kinovi may exceed it by at most one cent.
|
||||
let artcraft_cents = cost_cents(1);
|
||||
let kinovi_cents = kinovi.cost_in_usd_cents.unwrap();
|
||||
assert!(
|
||||
kinovi_cents == artcraft_cents || kinovi_cents == artcraft_cents + 1,
|
||||
"batch 1: artcraft={artcraft_cents}, kinovi={kinovi_cents}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_2_matches_kinovi() {
|
||||
let kinovi = KinoviMidjourney7CostState { batch_count: KinoviMidjourneyBatchCount::Two }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost_cents(2), kinovi.cost_in_usd_cents.unwrap());
|
||||
// NB: The kinovi provider estimate rounds fractional cents UP
|
||||
// (KinoviGenerationCost) while this artcraft user price rounds to
|
||||
// nearest — kinovi may exceed it by at most one cent.
|
||||
let artcraft_cents = cost_cents(2);
|
||||
let kinovi_cents = kinovi.cost_in_usd_cents.unwrap();
|
||||
assert!(
|
||||
kinovi_cents == artcraft_cents || kinovi_cents == artcraft_cents + 1,
|
||||
"batch 2: artcraft={artcraft_cents}, kinovi={kinovi_cents}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4_matches_kinovi() {
|
||||
let kinovi = KinoviMidjourney7CostState { batch_count: KinoviMidjourneyBatchCount::Four }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost_cents(4), kinovi.cost_in_usd_cents.unwrap());
|
||||
// NB: The kinovi provider estimate rounds fractional cents UP
|
||||
// (KinoviGenerationCost) while this artcraft user price rounds to
|
||||
// nearest — kinovi may exceed it by at most one cent.
|
||||
let artcraft_cents = cost_cents(4);
|
||||
let kinovi_cents = kinovi.cost_in_usd_cents.unwrap();
|
||||
assert!(
|
||||
kinovi_cents == artcraft_cents || kinovi_cents == artcraft_cents + 1,
|
||||
"batch 4: artcraft={artcraft_cents}, kinovi={kinovi_cents}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+27
-3
@@ -93,21 +93,45 @@ mod tests {
|
||||
fn batch_1_matches_kinovi() {
|
||||
let kinovi = KinoviMidjourney7NijiCostState { batch_count: KinoviMidjourneyBatchCount::One }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost_cents(1), kinovi.cost_in_usd_cents.unwrap());
|
||||
// NB: The kinovi provider estimate rounds fractional cents UP
|
||||
// (KinoviGenerationCost) while this artcraft user price rounds to
|
||||
// nearest — kinovi may exceed it by at most one cent.
|
||||
let artcraft_cents = cost_cents(1);
|
||||
let kinovi_cents = kinovi.cost_in_usd_cents.unwrap();
|
||||
assert!(
|
||||
kinovi_cents == artcraft_cents || kinovi_cents == artcraft_cents + 1,
|
||||
"batch 1: artcraft={artcraft_cents}, kinovi={kinovi_cents}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_2_matches_kinovi() {
|
||||
let kinovi = KinoviMidjourney7NijiCostState { batch_count: KinoviMidjourneyBatchCount::Two }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost_cents(2), kinovi.cost_in_usd_cents.unwrap());
|
||||
// NB: The kinovi provider estimate rounds fractional cents UP
|
||||
// (KinoviGenerationCost) while this artcraft user price rounds to
|
||||
// nearest — kinovi may exceed it by at most one cent.
|
||||
let artcraft_cents = cost_cents(2);
|
||||
let kinovi_cents = kinovi.cost_in_usd_cents.unwrap();
|
||||
assert!(
|
||||
kinovi_cents == artcraft_cents || kinovi_cents == artcraft_cents + 1,
|
||||
"batch 2: artcraft={artcraft_cents}, kinovi={kinovi_cents}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4_matches_kinovi() {
|
||||
let kinovi = KinoviMidjourney7NijiCostState { batch_count: KinoviMidjourneyBatchCount::Four }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost_cents(4), kinovi.cost_in_usd_cents.unwrap());
|
||||
// NB: The kinovi provider estimate rounds fractional cents UP
|
||||
// (KinoviGenerationCost) while this artcraft user price rounds to
|
||||
// nearest — kinovi may exceed it by at most one cent.
|
||||
let artcraft_cents = cost_cents(4);
|
||||
let kinovi_cents = kinovi.cost_in_usd_cents.unwrap();
|
||||
assert!(
|
||||
kinovi_cents == artcraft_cents || kinovi_cents == artcraft_cents + 1,
|
||||
"batch 4: artcraft={artcraft_cents}, kinovi={kinovi_cents}",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+27
-3
@@ -93,21 +93,45 @@ mod tests {
|
||||
fn batch_1_matches_kinovi() {
|
||||
let kinovi = KinoviMidjourney8CostState { batch_count: KinoviMidjourneyBatchCount::One }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost_cents(1), kinovi.cost_in_usd_cents.unwrap());
|
||||
// NB: The kinovi provider estimate rounds fractional cents UP
|
||||
// (KinoviGenerationCost) while this artcraft user price rounds to
|
||||
// nearest — kinovi may exceed it by at most one cent.
|
||||
let artcraft_cents = cost_cents(1);
|
||||
let kinovi_cents = kinovi.cost_in_usd_cents.unwrap();
|
||||
assert!(
|
||||
kinovi_cents == artcraft_cents || kinovi_cents == artcraft_cents + 1,
|
||||
"batch 1: artcraft={artcraft_cents}, kinovi={kinovi_cents}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_2_matches_kinovi() {
|
||||
let kinovi = KinoviMidjourney8CostState { batch_count: KinoviMidjourneyBatchCount::Two }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost_cents(2), kinovi.cost_in_usd_cents.unwrap());
|
||||
// NB: The kinovi provider estimate rounds fractional cents UP
|
||||
// (KinoviGenerationCost) while this artcraft user price rounds to
|
||||
// nearest — kinovi may exceed it by at most one cent.
|
||||
let artcraft_cents = cost_cents(2);
|
||||
let kinovi_cents = kinovi.cost_in_usd_cents.unwrap();
|
||||
assert!(
|
||||
kinovi_cents == artcraft_cents || kinovi_cents == artcraft_cents + 1,
|
||||
"batch 2: artcraft={artcraft_cents}, kinovi={kinovi_cents}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4_matches_kinovi() {
|
||||
let kinovi = KinoviMidjourney8CostState { batch_count: KinoviMidjourneyBatchCount::Four }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost_cents(4), kinovi.cost_in_usd_cents.unwrap());
|
||||
// NB: The kinovi provider estimate rounds fractional cents UP
|
||||
// (KinoviGenerationCost) while this artcraft user price rounds to
|
||||
// nearest — kinovi may exceed it by at most one cent.
|
||||
let artcraft_cents = cost_cents(4);
|
||||
let kinovi_cents = kinovi.cost_in_usd_cents.unwrap();
|
||||
assert!(
|
||||
kinovi_cents == artcraft_cents || kinovi_cents == artcraft_cents + 1,
|
||||
"batch 4: artcraft={artcraft_cents}, kinovi={kinovi_cents}",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-4
@@ -37,8 +37,9 @@ impl KinoviMidjourney7CostState {
|
||||
reference_image_urls: None,
|
||||
};
|
||||
|
||||
let cost_in_credits = pricing_request.estimate_credits() as u64;
|
||||
let cost_in_usd_cents = pricing_request.estimate_cost_in_usd_cents();
|
||||
let costs = pricing_request.calculate_costs();
|
||||
let cost_in_credits = costs.kinovi_credits;
|
||||
let cost_in_usd_cents = costs.usd_cents_rounded_up;
|
||||
|
||||
ImageGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_credits),
|
||||
@@ -93,13 +94,13 @@ mod tests {
|
||||
#[test]
|
||||
fn batch_one_is_six_cents() {
|
||||
// 12 / 193 × 100 = 6.21 → 6¢
|
||||
assert_eq!(usd_cents_for(KinoviMidjourneyBatchCount::One), 6);
|
||||
assert_eq!(usd_cents_for(KinoviMidjourneyBatchCount::One), 7); // rounds UP
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_two_is_twelve_cents() {
|
||||
// 24 / 193 × 100 = 12.44 → 12¢
|
||||
assert_eq!(usd_cents_for(KinoviMidjourneyBatchCount::Two), 12);
|
||||
assert_eq!(usd_cents_for(KinoviMidjourneyBatchCount::Two), 13); // rounds UP
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+5
-4
@@ -35,8 +35,9 @@ impl KinoviMidjourney7NijiCostState {
|
||||
reference_image_urls: None,
|
||||
};
|
||||
|
||||
let cost_in_credits = pricing_request.estimate_credits() as u64;
|
||||
let cost_in_usd_cents = pricing_request.estimate_cost_in_usd_cents();
|
||||
let costs = pricing_request.calculate_costs();
|
||||
let cost_in_credits = costs.kinovi_credits;
|
||||
let cost_in_usd_cents = costs.usd_cents_rounded_up;
|
||||
|
||||
ImageGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_credits),
|
||||
@@ -59,7 +60,7 @@ mod tests {
|
||||
let cost = KinoviMidjourney7NijiCostState { batch_count: KinoviMidjourneyBatchCount::One }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost.cost_in_credits, Some(12));
|
||||
assert_eq!(cost.cost_in_usd_cents, Some(6));
|
||||
assert_eq!(cost.cost_in_usd_cents, Some(7)); // 1200/193 = 6.22 -> rounds UP
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -67,7 +68,7 @@ mod tests {
|
||||
let cost = KinoviMidjourney7NijiCostState { batch_count: KinoviMidjourneyBatchCount::Two }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost.cost_in_credits, Some(24));
|
||||
assert_eq!(cost.cost_in_usd_cents, Some(12));
|
||||
assert_eq!(cost.cost_in_usd_cents, Some(13)); // 2400/193 = 12.44 -> rounds UP
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+5
-4
@@ -35,8 +35,9 @@ impl KinoviMidjourney8CostState {
|
||||
reference_image_urls: None,
|
||||
};
|
||||
|
||||
let cost_in_credits = pricing_request.estimate_credits() as u64;
|
||||
let cost_in_usd_cents = pricing_request.estimate_cost_in_usd_cents();
|
||||
let costs = pricing_request.calculate_costs();
|
||||
let cost_in_credits = costs.kinovi_credits;
|
||||
let cost_in_usd_cents = costs.usd_cents_rounded_up;
|
||||
|
||||
ImageGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_credits),
|
||||
@@ -59,7 +60,7 @@ mod tests {
|
||||
let cost = KinoviMidjourney8CostState { batch_count: KinoviMidjourneyBatchCount::One }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost.cost_in_credits, Some(12));
|
||||
assert_eq!(cost.cost_in_usd_cents, Some(6));
|
||||
assert_eq!(cost.cost_in_usd_cents, Some(7)); // 1200/193 = 6.22 -> rounds UP
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -67,7 +68,7 @@ mod tests {
|
||||
let cost = KinoviMidjourney8CostState { batch_count: KinoviMidjourneyBatchCount::Two }
|
||||
.estimate_cost();
|
||||
assert_eq!(cost.cost_in_credits, Some(24));
|
||||
assert_eq!(cost.cost_in_usd_cents, Some(12));
|
||||
assert_eq!(cost.cost_in_usd_cents, Some(13)); // 2400/193 = 12.44 -> rounds UP
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+10
-4
@@ -234,10 +234,16 @@ mod tests {
|
||||
.estimate_cost()
|
||||
.expect("kinovi estimate_cost");
|
||||
|
||||
assert_eq!(
|
||||
artcraft_cost.cost_in_usd_cents, kinovi_cost.cost_in_usd_cents,
|
||||
"USD cents mismatch: res={:?} dur={}s batch={}",
|
||||
res, dur, batch,
|
||||
// NB: The kinovi provider estimate rounds fractional cents UP
|
||||
// (KinoviGenerationCost), while the artcraft user-facing price
|
||||
// here rounds to nearest — so kinovi may exceed artcraft by at
|
||||
// most one cent.
|
||||
let artcraft_cents = artcraft_cost.cost_in_usd_cents.expect("artcraft cents");
|
||||
let kinovi_cents = kinovi_cost.cost_in_usd_cents.expect("kinovi cents");
|
||||
assert!(
|
||||
kinovi_cents == artcraft_cents || kinovi_cents == artcraft_cents + 1,
|
||||
"USD cents mismatch: res={:?} dur={}s batch={} (artcraft={}, kinovi={})",
|
||||
res, dur, batch, artcraft_cents, kinovi_cents,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+10
-4
@@ -248,10 +248,16 @@ mod tests {
|
||||
.estimate_cost()
|
||||
.expect("kinovi estimate_cost");
|
||||
|
||||
assert_eq!(
|
||||
artcraft_cost.cost_in_usd_cents, kinovi_cost.cost_in_usd_cents,
|
||||
"USD cents mismatch: res={:?} dur={}s batch={}",
|
||||
res, dur, batch,
|
||||
// NB: The kinovi provider estimate rounds fractional cents UP
|
||||
// (KinoviGenerationCost), while the artcraft user-facing price
|
||||
// here rounds to nearest — so kinovi may exceed artcraft by at
|
||||
// most one cent.
|
||||
let artcraft_cents = artcraft_cost.cost_in_usd_cents.expect("artcraft cents");
|
||||
let kinovi_cents = kinovi_cost.cost_in_usd_cents.expect("kinovi cents");
|
||||
assert!(
|
||||
kinovi_cents == artcraft_cents || kinovi_cents == artcraft_cents + 1,
|
||||
"USD cents mismatch: res={:?} dur={}s batch={} (artcraft={}, kinovi={})",
|
||||
res, dur, batch, artcraft_cents, kinovi_cents,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -42,11 +42,12 @@ impl KinoviHappyHorse1p0CostState {
|
||||
start_frame_url: None,
|
||||
};
|
||||
|
||||
let cost_in_credits = pricing_request.estimate_credits();
|
||||
let cost_in_usd_cents = pricing_request.estimate_cost_in_usd_cents();
|
||||
let costs = pricing_request.calculate_costs();
|
||||
let cost_in_credits = costs.kinovi_credits;
|
||||
let cost_in_usd_cents = costs.usd_cents_rounded_up;
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_credits as u64),
|
||||
cost_in_credits: Some(cost_in_credits),
|
||||
cost_in_usd_cents: Some(cost_in_usd_cents),
|
||||
is_free: false,
|
||||
is_unlimited: false,
|
||||
@@ -98,9 +99,9 @@ mod tests {
|
||||
#[test]
|
||||
fn usd_cents_720p() {
|
||||
// 165 credits / 193 * 100 = 85.49 → 85¢
|
||||
assert_eq!(usd_cents(None, 5, None), 85);
|
||||
assert_eq!(usd_cents(None, 5, None), 86); // 16500/193 = 85.49 -> rounds UP
|
||||
// 495 credits / 193 * 100 = 256.48 → 256¢
|
||||
assert_eq!(usd_cents(None, 15, None), 256);
|
||||
assert_eq!(usd_cents(None, 15, None), 257); // 49500/193 = 256.48 -> rounds UP
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+34
-22
@@ -46,23 +46,31 @@ impl KinoviSeedance2p0CostState {
|
||||
duration_seconds: self.duration_seconds,
|
||||
batch_count: self.batch_count,
|
||||
|
||||
// PRESENCE of reference videos changes the price (per-second
|
||||
// surcharge); the URL contents don't.
|
||||
reference_video_urls: if self.has_video_reference {
|
||||
Some(vec!["pricing-placeholder".to_string()])
|
||||
} else {
|
||||
None
|
||||
},
|
||||
|
||||
// No impact on price
|
||||
prompt: String::new(),
|
||||
aspect_ratio: None,
|
||||
start_frame_url: None,
|
||||
end_frame_url: None,
|
||||
reference_image_urls: None,
|
||||
reference_video_urls: None,
|
||||
reference_audio_urls: None,
|
||||
character_ids: None,
|
||||
use_face_blur_hack: None,
|
||||
};
|
||||
|
||||
let cost_in_credits = pricing_request.estimate_credits();
|
||||
let cost_in_usd_cents = pricing_request.estimate_cost_in_usd_cents();
|
||||
let costs = pricing_request.calculate_costs();
|
||||
let cost_in_credits = costs.kinovi_credits;
|
||||
let cost_in_usd_cents = costs.usd_cents_rounded_up;
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_credits as u64),
|
||||
cost_in_credits: Some(cost_in_credits),
|
||||
cost_in_usd_cents: Some(cost_in_usd_cents),
|
||||
is_free: false,
|
||||
is_unlimited: false,
|
||||
@@ -97,16 +105,16 @@ mod tests {
|
||||
fn cost_720p_batch_1() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One), 83);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 104);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 6, KinoviBatchCount::One), 124);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 7, KinoviBatchCount::One), 145);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One), 207);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 6, KinoviBatchCount::One), 125);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 7, KinoviBatchCount::One), 146);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One), 208);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One), 311);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_2() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::Two), 166);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Two), 207);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Two), 208);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::Two), 622);
|
||||
}
|
||||
|
||||
@@ -125,7 +133,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cost_480p_batch_1() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 4, KinoviBatchCount::One), 31);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 4, KinoviBatchCount::One), 32);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 39);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 10, KinoviBatchCount::One), 78);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 15, KinoviBatchCount::One), 117);
|
||||
@@ -138,7 +146,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cost_480p_batch_4() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Four), 155);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Four), 156);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,14 +158,14 @@ mod tests {
|
||||
#[test]
|
||||
fn cost_1080p_batch_1() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 4, KinoviBatchCount::One), 187);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::One), 233);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 10, KinoviBatchCount::One), 466);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 15, KinoviBatchCount::One), 699);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::One), 234);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 10, KinoviBatchCount::One), 467);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 15, KinoviBatchCount::One), 700);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_1080p_batch_2() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::Two), 466);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::TenEightyP, 5, KinoviBatchCount::Two), 467);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -202,7 +210,7 @@ mod tests {
|
||||
// -- Video reference does NOT affect cost (yet) --
|
||||
|
||||
#[test]
|
||||
fn video_reference_does_not_affect_cost() {
|
||||
fn video_reference_adds_surcharge() {
|
||||
let base = KinoviSeedance2p0CostState {
|
||||
resolution: Some(KinoviOutputResolution::SevenTwentyP),
|
||||
duration_seconds: 5,
|
||||
@@ -211,8 +219,10 @@ mod tests {
|
||||
};
|
||||
let without = base.estimate_cost();
|
||||
let with = KinoviSeedance2p0CostState { has_video_reference: true, ..base }.estimate_cost();
|
||||
assert_eq!(without.cost_in_usd_cents, with.cost_in_usd_cents);
|
||||
assert_eq!(without.cost_in_credits, with.cost_in_credits);
|
||||
// 720p surcharge is +8 credits/s: 200 -> 240 credits (24000/193 = 124.35 -> 125 cents).
|
||||
assert_eq!(without.cost_in_credits, Some(200));
|
||||
assert_eq!(with.cost_in_credits, Some(240));
|
||||
assert_eq!(with.cost_in_usd_cents, Some(125));
|
||||
}
|
||||
|
||||
// ── from_request() tests ──
|
||||
@@ -250,7 +260,7 @@ mod tests {
|
||||
fn from_request_1080p_batch_2() {
|
||||
let req = make_request_state(Some(KinoviOutputResolution::TenEightyP), 5, KinoviBatchCount::Two, false);
|
||||
let cost = KinoviSeedance2p0CostState::from_request(&req);
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(466));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(467));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -258,8 +268,9 @@ mod tests {
|
||||
let req = make_request_state(Some(KinoviOutputResolution::SevenTwentyP), 5, KinoviBatchCount::One, true);
|
||||
let cost = KinoviSeedance2p0CostState::from_request(&req);
|
||||
assert!(cost.has_video_reference);
|
||||
// Video refs don't affect cost yet
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(104));
|
||||
// Video refs add a +8 credits/s surcharge at 720p: 240 credits -> 125 cents.
|
||||
assert_eq!(cost.estimate_cost().cost_in_credits, Some(240));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(125));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -308,8 +319,9 @@ mod tests {
|
||||
let draft = make_draft(5, 1, None, true);
|
||||
let cost = KinoviSeedance2p0CostState::from_draft(&draft);
|
||||
assert!(cost.has_video_reference);
|
||||
// Video refs don't affect cost yet
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(104));
|
||||
// Video refs add a +8 credits/s surcharge at 720p: 240 credits -> 125 cents.
|
||||
assert_eq!(cost.estimate_cost().cost_in_credits, Some(240));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(125));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+33
-21
@@ -46,23 +46,31 @@ impl KinoviSeedance2p0FastCostState {
|
||||
duration_seconds: self.duration_seconds,
|
||||
batch_count: self.batch_count,
|
||||
|
||||
// PRESENCE of reference videos changes the price (per-second
|
||||
// surcharge); the URL contents don't.
|
||||
reference_video_urls: if self.has_video_reference {
|
||||
Some(vec!["pricing-placeholder".to_string()])
|
||||
} else {
|
||||
None
|
||||
},
|
||||
|
||||
// No impact on price
|
||||
prompt: String::new(),
|
||||
aspect_ratio: None,
|
||||
start_frame_url: None,
|
||||
end_frame_url: None,
|
||||
reference_image_urls: None,
|
||||
reference_video_urls: None,
|
||||
reference_audio_urls: None,
|
||||
character_ids: None,
|
||||
use_face_blur_hack: None,
|
||||
};
|
||||
|
||||
let cost_in_credits = pricing_request.estimate_credits();
|
||||
let cost_in_usd_cents = pricing_request.estimate_cost_in_usd_cents();
|
||||
let costs = pricing_request.calculate_costs();
|
||||
let cost_in_credits = costs.kinovi_credits;
|
||||
let cost_in_usd_cents = costs.usd_cents_rounded_up;
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: Some(cost_in_credits as u64),
|
||||
cost_in_credits: Some(cost_in_credits),
|
||||
cost_in_usd_cents: Some(cost_in_usd_cents),
|
||||
is_free: false,
|
||||
is_unlimited: false,
|
||||
@@ -99,20 +107,20 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_1() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One), 58);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 4, KinoviBatchCount::One), 59);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::One), 73);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One), 145);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 10, KinoviBatchCount::One), 146);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 15, KinoviBatchCount::One), 218);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_2() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Two), 145);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Two), 146);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_720p_batch_4() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Four), 290);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::SevenTwentyP, 5, KinoviBatchCount::Four), 291);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,8 +131,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cost_480p_batch_1() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 4, KinoviBatchCount::One), 29);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 36);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 4, KinoviBatchCount::One), 30);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::One), 37);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 10, KinoviBatchCount::One), 73);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 15, KinoviBatchCount::One), 109);
|
||||
}
|
||||
@@ -136,7 +144,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cost_480p_batch_4() {
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Four), 145);
|
||||
assert_eq!(usd_cents(KinoviOutputResolution::FourEightyP, 5, KinoviBatchCount::Four), 146);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,7 +182,7 @@ mod tests {
|
||||
// -- Video reference does NOT affect cost (yet) --
|
||||
|
||||
#[test]
|
||||
fn video_reference_does_not_affect_cost() {
|
||||
fn video_reference_adds_surcharge() {
|
||||
let base = KinoviSeedance2p0FastCostState {
|
||||
resolution: Some(KinoviOutputResolution::SevenTwentyP),
|
||||
duration_seconds: 5,
|
||||
@@ -183,8 +191,10 @@ mod tests {
|
||||
};
|
||||
let without = base.estimate_cost();
|
||||
let with = KinoviSeedance2p0FastCostState { has_video_reference: true, ..base }.estimate_cost();
|
||||
assert_eq!(without.cost_in_usd_cents, with.cost_in_usd_cents);
|
||||
assert_eq!(without.cost_in_credits, with.cost_in_credits);
|
||||
// Fast 720p surcharge is +6 credits/s: 140 -> 170 credits (17000/193 = 88.08 -> 89 cents).
|
||||
assert_eq!(without.cost_in_credits, Some(140));
|
||||
assert_eq!(with.cost_in_credits, Some(170));
|
||||
assert_eq!(with.cost_in_usd_cents, Some(89));
|
||||
}
|
||||
|
||||
// ── from_request() tests ──
|
||||
@@ -215,7 +225,7 @@ mod tests {
|
||||
fn from_request_480p() {
|
||||
let req = make_request_state(Some(KinoviOutputResolution::FourEightyP), 5, KinoviBatchCount::One, false);
|
||||
let cost = KinoviSeedance2p0FastCostState::from_request(&req);
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(36));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(37));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -223,8 +233,9 @@ mod tests {
|
||||
let req = make_request_state(Some(KinoviOutputResolution::SevenTwentyP), 5, KinoviBatchCount::One, true);
|
||||
let cost = KinoviSeedance2p0FastCostState::from_request(&req);
|
||||
assert!(cost.has_video_reference);
|
||||
// Video refs don't affect cost yet
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(73));
|
||||
// Video refs add a +6 credits/s surcharge at 720p: 170 credits -> 89 cents.
|
||||
assert_eq!(cost.estimate_cost().cost_in_credits, Some(170));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(89));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -256,7 +267,7 @@ mod tests {
|
||||
let draft = make_draft(5, 1, Some(RouterResolution::FourEightyP), false);
|
||||
let cost = KinoviSeedance2p0FastCostState::from_draft(&draft);
|
||||
assert!(matches!(cost.resolution, Some(KinoviOutputResolution::FourEightyP)));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(36));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(37));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -264,8 +275,9 @@ mod tests {
|
||||
let draft = make_draft(5, 1, None, true);
|
||||
let cost = KinoviSeedance2p0FastCostState::from_draft(&draft);
|
||||
assert!(cost.has_video_reference);
|
||||
// Video refs don't affect cost yet
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(73));
|
||||
// Video refs add a +6 credits/s surcharge at 720p: 170 credits -> 89 cents.
|
||||
assert_eq!(cost.estimate_cost().cost_in_credits, Some(170));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(89));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -281,7 +293,7 @@ mod tests {
|
||||
let cost = KinoviSeedance2p0FastCostState::from_draft(&draft);
|
||||
assert_eq!(cost.duration_seconds, 15);
|
||||
assert!(matches!(cost.batch_count, Some(KinoviBatchCount::Two)));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(435));
|
||||
assert_eq!(cost.estimate_cost().cost_in_usd_cents, Some(436));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/// Kinovi credits per US dollar.
|
||||
/// Credit package: 22,000 credits for $114 (~192.98 credits/$1, rounded to 193).
|
||||
const CREDITS_PER_DOLLAR: u64 = 193;
|
||||
|
||||
/// The cost of a Kinovi generation, in both native credits and USD cents.
|
||||
pub struct KinoviGenerationCost {
|
||||
/// The cost of the generation in Kinovi credits.
|
||||
pub kinovi_credits: u64,
|
||||
|
||||
/// Estimated cost in cents, rounded up to the nearest whole cent.
|
||||
/// This does not account for discounts, prorations, etc.
|
||||
/// This is always rounded up when fractional.
|
||||
pub usd_cents_rounded_up: u64,
|
||||
}
|
||||
|
||||
impl KinoviGenerationCost {
|
||||
/// Build a cost from a Kinovi credit amount, deriving the USD cents via
|
||||
/// the credit-package rate (always rounding up fractional cents).
|
||||
pub fn from_kinovi_credits(kinovi_credits: u64) -> Self {
|
||||
// Integer ceiling of (credits * 100 / CREDITS_PER_DOLLAR).
|
||||
let usd_cents_rounded_up = (kinovi_credits * 100).div_ceil(CREDITS_PER_DOLLAR);
|
||||
Self {
|
||||
kinovi_credits,
|
||||
usd_cents_rounded_up,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn zero_credits_is_zero_cents() {
|
||||
let cost = KinoviGenerationCost::from_kinovi_credits(0);
|
||||
assert_eq!(cost.kinovi_credits, 0);
|
||||
assert_eq!(cost.usd_cents_rounded_up, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exact_dollar_boundaries_do_not_round() {
|
||||
// 193 credits = exactly $1.00.
|
||||
let cost = KinoviGenerationCost::from_kinovi_credits(193);
|
||||
assert_eq!(cost.kinovi_credits, 193);
|
||||
assert_eq!(cost.usd_cents_rounded_up, 100);
|
||||
|
||||
let cost = KinoviGenerationCost::from_kinovi_credits(386);
|
||||
assert_eq!(cost.usd_cents_rounded_up, 200);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fractional_cents_round_up() {
|
||||
// 194 credits = 100.518... cents -> 101.
|
||||
let cost = KinoviGenerationCost::from_kinovi_credits(194);
|
||||
assert_eq!(cost.usd_cents_rounded_up, 101);
|
||||
|
||||
// 1 credit = 0.518... cents -> 1.
|
||||
let cost = KinoviGenerationCost::from_kinovi_credits(1);
|
||||
assert_eq!(cost.usd_cents_rounded_up, 1);
|
||||
}
|
||||
}
|
||||
+20
-10
@@ -1,3 +1,4 @@
|
||||
use crate::generate::cost::KinoviGenerationCost;
|
||||
use crate::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use crate::error::seedance2pro_error::Seedance2ProError;
|
||||
use crate::requests::generate_image::generate_image::{
|
||||
@@ -36,12 +37,21 @@ pub struct GenerateMidjourneyV7Request {
|
||||
}
|
||||
|
||||
impl GenerateMidjourneyV7Request {
|
||||
pub fn estimate_credits(&self) -> u32 {
|
||||
self.to_inner_request().estimate_credits()
|
||||
/// Calculate the cost of this request, in Kinovi credits and USD cents
|
||||
/// (rounded up).
|
||||
pub fn calculate_costs(&self) -> KinoviGenerationCost {
|
||||
self.to_inner_request().calculate_costs()
|
||||
}
|
||||
|
||||
#[deprecated(note = "Use calculate_costs() instead")]
|
||||
pub fn estimate_credits(&self) -> u32 {
|
||||
self.calculate_costs().kinovi_credits as u32
|
||||
}
|
||||
|
||||
/// NB: Rounds UP fractional cents (the historical behavior rounded to nearest).
|
||||
#[deprecated(note = "Use calculate_costs() instead")]
|
||||
pub fn estimate_cost_in_usd_cents(&self) -> u64 {
|
||||
self.to_inner_request().estimate_cost_in_usd_cents()
|
||||
self.calculate_costs().usd_cents_rounded_up
|
||||
}
|
||||
|
||||
pub(crate) fn to_inner_request(&self) -> KinoviGenerateImageRequest {
|
||||
@@ -264,27 +274,27 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn batch_one_is_twelve_credits() {
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::One).estimate_credits(), 12);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::One).calculate_costs().kinovi_credits, 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_two_is_twentyfour_credits() {
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Two).estimate_credits(), 24);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Two).calculate_costs().kinovi_credits, 24);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_four_is_fortyeight_credits() {
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Four).estimate_credits(), 48);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Four).calculate_costs().kinovi_credits, 48);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_batch_one_is_six() {
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::One).estimate_cost_in_usd_cents(), 6);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::One).calculate_costs().usd_cents_rounded_up, 7); // 1200/193 = 6.22 -> rounds UP
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_batch_four_is_twentyfive() {
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Four).estimate_cost_in_usd_cents(), 25);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Four).calculate_costs().usd_cents_rounded_up, 25); // 4800/193 = 24.87 -> rounds UP
|
||||
}
|
||||
|
||||
/// Pricing must match the inner module byte-for-byte (sanity that no
|
||||
@@ -298,8 +308,8 @@ mod tests {
|
||||
] {
|
||||
let outer = make_request(batch);
|
||||
let inner = outer.to_inner_request();
|
||||
assert_eq!(outer.estimate_credits(), inner.estimate_credits(), "batch={:?}", batch);
|
||||
assert_eq!(outer.estimate_cost_in_usd_cents(), inner.estimate_cost_in_usd_cents(), "batch={:?}", batch);
|
||||
assert_eq!(outer.calculate_costs().kinovi_credits, inner.calculate_costs().kinovi_credits, "batch={:?}", batch);
|
||||
assert_eq!(outer.calculate_costs().usd_cents_rounded_up, inner.calculate_costs().usd_cents_rounded_up, "batch={:?}", batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-10
@@ -1,3 +1,4 @@
|
||||
use crate::generate::cost::KinoviGenerationCost;
|
||||
use crate::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use crate::error::seedance2pro_error::Seedance2ProError;
|
||||
use crate::requests::generate_image::generate_image::{
|
||||
@@ -36,12 +37,21 @@ pub struct GenerateMidjourneyV7NijiRequest {
|
||||
}
|
||||
|
||||
impl GenerateMidjourneyV7NijiRequest {
|
||||
pub fn estimate_credits(&self) -> u32 {
|
||||
self.to_inner_request().estimate_credits()
|
||||
/// Calculate the cost of this request, in Kinovi credits and USD cents
|
||||
/// (rounded up).
|
||||
pub fn calculate_costs(&self) -> KinoviGenerationCost {
|
||||
self.to_inner_request().calculate_costs()
|
||||
}
|
||||
|
||||
#[deprecated(note = "Use calculate_costs() instead")]
|
||||
pub fn estimate_credits(&self) -> u32 {
|
||||
self.calculate_costs().kinovi_credits as u32
|
||||
}
|
||||
|
||||
/// NB: Rounds UP fractional cents (the historical behavior rounded to nearest).
|
||||
#[deprecated(note = "Use calculate_costs() instead")]
|
||||
pub fn estimate_cost_in_usd_cents(&self) -> u64 {
|
||||
self.to_inner_request().estimate_cost_in_usd_cents()
|
||||
self.calculate_costs().usd_cents_rounded_up
|
||||
}
|
||||
|
||||
pub(crate) fn to_inner_request(&self) -> KinoviGenerateImageRequest {
|
||||
@@ -262,27 +272,27 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn batch_one_is_twelve_credits() {
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::One).estimate_credits(), 12);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::One).calculate_costs().kinovi_credits, 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_two_is_twentyfour_credits() {
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Two).estimate_credits(), 24);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Two).calculate_costs().kinovi_credits, 24);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_four_is_fortyeight_credits() {
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Four).estimate_credits(), 48);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Four).calculate_costs().kinovi_credits, 48);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_batch_one_is_six() {
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::One).estimate_cost_in_usd_cents(), 6);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::One).calculate_costs().usd_cents_rounded_up, 7); // 1200/193 = 6.22 -> rounds UP
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_batch_four_is_twentyfive() {
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Four).estimate_cost_in_usd_cents(), 25);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Four).calculate_costs().usd_cents_rounded_up, 25); // 4800/193 = 24.87 -> rounds UP
|
||||
}
|
||||
|
||||
/// Pricing must match the inner module exactly.
|
||||
@@ -295,8 +305,8 @@ mod tests {
|
||||
] {
|
||||
let outer = make_request(batch);
|
||||
let inner = outer.to_inner_request();
|
||||
assert_eq!(outer.estimate_credits(), inner.estimate_credits(), "batch={:?}", batch);
|
||||
assert_eq!(outer.estimate_cost_in_usd_cents(), inner.estimate_cost_in_usd_cents(), "batch={:?}", batch);
|
||||
assert_eq!(outer.calculate_costs().kinovi_credits, inner.calculate_costs().kinovi_credits, "batch={:?}", batch);
|
||||
assert_eq!(outer.calculate_costs().usd_cents_rounded_up, inner.calculate_costs().usd_cents_rounded_up, "batch={:?}", batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+20
-10
@@ -1,3 +1,4 @@
|
||||
use crate::generate::cost::KinoviGenerationCost;
|
||||
use crate::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use crate::error::seedance2pro_error::Seedance2ProError;
|
||||
use crate::requests::generate_image::generate_image::{
|
||||
@@ -36,12 +37,21 @@ pub struct GenerateMidjourneyV8Request {
|
||||
}
|
||||
|
||||
impl GenerateMidjourneyV8Request {
|
||||
pub fn estimate_credits(&self) -> u32 {
|
||||
self.to_inner_request().estimate_credits()
|
||||
/// Calculate the cost of this request, in Kinovi credits and USD cents
|
||||
/// (rounded up).
|
||||
pub fn calculate_costs(&self) -> KinoviGenerationCost {
|
||||
self.to_inner_request().calculate_costs()
|
||||
}
|
||||
|
||||
#[deprecated(note = "Use calculate_costs() instead")]
|
||||
pub fn estimate_credits(&self) -> u32 {
|
||||
self.calculate_costs().kinovi_credits as u32
|
||||
}
|
||||
|
||||
/// NB: Rounds UP fractional cents (the historical behavior rounded to nearest).
|
||||
#[deprecated(note = "Use calculate_costs() instead")]
|
||||
pub fn estimate_cost_in_usd_cents(&self) -> u64 {
|
||||
self.to_inner_request().estimate_cost_in_usd_cents()
|
||||
self.calculate_costs().usd_cents_rounded_up
|
||||
}
|
||||
|
||||
pub(crate) fn to_inner_request(&self) -> KinoviGenerateImageRequest {
|
||||
@@ -259,27 +269,27 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn batch_one_is_twelve_credits() {
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::One).estimate_credits(), 12);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::One).calculate_costs().kinovi_credits, 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_two_is_twentyfour_credits() {
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Two).estimate_credits(), 24);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Two).calculate_costs().kinovi_credits, 24);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_four_is_fortyeight_credits() {
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Four).estimate_credits(), 48);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Four).calculate_costs().kinovi_credits, 48);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_batch_one_is_six() {
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::One).estimate_cost_in_usd_cents(), 6);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::One).calculate_costs().usd_cents_rounded_up, 7); // 1200/193 = 6.22 -> rounds UP
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_batch_four_is_twentyfive() {
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Four).estimate_cost_in_usd_cents(), 25);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Four).calculate_costs().usd_cents_rounded_up, 25); // 4800/193 = 24.87 -> rounds UP
|
||||
}
|
||||
|
||||
/// Pricing must match the inner module exactly.
|
||||
@@ -292,8 +302,8 @@ mod tests {
|
||||
] {
|
||||
let outer = make_request(batch);
|
||||
let inner = outer.to_inner_request();
|
||||
assert_eq!(outer.estimate_credits(), inner.estimate_credits(), "batch={:?}", batch);
|
||||
assert_eq!(outer.estimate_cost_in_usd_cents(), inner.estimate_cost_in_usd_cents(), "batch={:?}", batch);
|
||||
assert_eq!(outer.calculate_costs().kinovi_credits, inner.calculate_costs().kinovi_credits, "batch={:?}", batch);
|
||||
assert_eq!(outer.calculate_costs().usd_cents_rounded_up, inner.calculate_costs().usd_cents_rounded_up, "batch={:?}", batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod cost;
|
||||
pub mod image;
|
||||
pub mod video;
|
||||
|
||||
+95
-77
@@ -1,5 +1,6 @@
|
||||
use crate::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use crate::error::seedance2pro_error::Seedance2ProError;
|
||||
use crate::generate::cost::KinoviGenerationCost;
|
||||
use crate::requests::kinovi_host::KinoviHost;
|
||||
use crate::requests::workflow_run_task::workflow_run_task::{
|
||||
workflow_run_task, KinoviAspectRatioRaw, KinoviBatchCountRaw,
|
||||
@@ -66,32 +67,35 @@ pub enum KinoviHappyHorse1p0BatchCount {
|
||||
|
||||
impl GenerateHappyHorse1p0Request {
|
||||
/// Estimate the credit cost for this generation request.
|
||||
pub fn estimate_credits(&self) -> u32 {
|
||||
let credits_per_second: u32 = match self.output_resolution {
|
||||
/// Calculate the cost of this generation request, in Kinovi credits and
|
||||
/// USD cents (rounded up).
|
||||
pub fn calculate_costs(&self) -> KinoviGenerationCost {
|
||||
let credits_per_second: u64 = match self.output_resolution {
|
||||
Some(KinoviHappyHorse1p0OutputResolution::TenEightyP) => 66,
|
||||
Some(KinoviHappyHorse1p0OutputResolution::SevenTwentyP) | None => 33,
|
||||
};
|
||||
|
||||
let per_video = u32::from(self.duration_seconds) * credits_per_second;
|
||||
let batch_multiplier: u32 = match self.batch_count {
|
||||
let per_video = u64::from(self.duration_seconds) * credits_per_second;
|
||||
let batch_multiplier: u64 = match self.batch_count {
|
||||
None | Some(KinoviHappyHorse1p0BatchCount::One) => 1,
|
||||
Some(KinoviHappyHorse1p0BatchCount::Two) => 2,
|
||||
Some(KinoviHappyHorse1p0BatchCount::Four) => 4,
|
||||
};
|
||||
per_video * batch_multiplier
|
||||
|
||||
KinoviGenerationCost::from_kinovi_credits(per_video * batch_multiplier)
|
||||
}
|
||||
|
||||
/// Credits per dollar for billing conversion.
|
||||
/// 22,000 credits / $114 ≈ 192.98, rounded to 193.
|
||||
fn credits_per_dollar() -> f64 {
|
||||
193.0
|
||||
/// Estimate the credit cost for this generation request.
|
||||
#[deprecated(note = "Use calculate_costs() instead")]
|
||||
pub fn estimate_credits(&self) -> u32 {
|
||||
self.calculate_costs().kinovi_credits as u32
|
||||
}
|
||||
|
||||
/// Estimate the USD cost in cents for this generation request.
|
||||
/// NB: Rounds UP fractional cents (the historical behavior rounded to nearest).
|
||||
#[deprecated(note = "Use calculate_costs() instead")]
|
||||
pub fn estimate_cost_in_usd_cents(&self) -> u64 {
|
||||
let credits = self.estimate_credits() as f64;
|
||||
let cost = credits / Self::credits_per_dollar() * 100.0;
|
||||
cost.round() as u64
|
||||
self.calculate_costs().usd_cents_rounded_up
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,25 +219,25 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn every_duration() {
|
||||
assert_eq!(r720(3).estimate_credits(), 99);
|
||||
assert_eq!(r720(4).estimate_credits(), 132);
|
||||
assert_eq!(r720(5).estimate_credits(), 165);
|
||||
assert_eq!(r720(6).estimate_credits(), 198);
|
||||
assert_eq!(r720(7).estimate_credits(), 231);
|
||||
assert_eq!(r720(8).estimate_credits(), 264);
|
||||
assert_eq!(r720(9).estimate_credits(), 297);
|
||||
assert_eq!(r720(10).estimate_credits(), 330);
|
||||
assert_eq!(r720(11).estimate_credits(), 363);
|
||||
assert_eq!(r720(12).estimate_credits(), 396);
|
||||
assert_eq!(r720(13).estimate_credits(), 429);
|
||||
assert_eq!(r720(14).estimate_credits(), 462);
|
||||
assert_eq!(r720(15).estimate_credits(), 495);
|
||||
assert_eq!(r720(3).calculate_costs().kinovi_credits, 99);
|
||||
assert_eq!(r720(4).calculate_costs().kinovi_credits, 132);
|
||||
assert_eq!(r720(5).calculate_costs().kinovi_credits, 165);
|
||||
assert_eq!(r720(6).calculate_costs().kinovi_credits, 198);
|
||||
assert_eq!(r720(7).calculate_costs().kinovi_credits, 231);
|
||||
assert_eq!(r720(8).calculate_costs().kinovi_credits, 264);
|
||||
assert_eq!(r720(9).calculate_costs().kinovi_credits, 297);
|
||||
assert_eq!(r720(10).calculate_costs().kinovi_credits, 330);
|
||||
assert_eq!(r720(11).calculate_costs().kinovi_credits, 363);
|
||||
assert_eq!(r720(12).calculate_costs().kinovi_credits, 396);
|
||||
assert_eq!(r720(13).calculate_costs().kinovi_credits, 429);
|
||||
assert_eq!(r720(14).calculate_costs().kinovi_credits, 462);
|
||||
assert_eq!(r720(15).calculate_costs().kinovi_credits, 495);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_720p_same_as_default() {
|
||||
let default = r720(5).estimate_credits();
|
||||
let explicit = make_request(5, Some(KinoviHappyHorse1p0OutputResolution::SevenTwentyP), None).estimate_credits();
|
||||
let default = r720(5).calculate_costs().kinovi_credits;
|
||||
let explicit = make_request(5, Some(KinoviHappyHorse1p0OutputResolution::SevenTwentyP), None).calculate_costs().kinovi_credits;
|
||||
assert_eq!(default, explicit);
|
||||
}
|
||||
}
|
||||
@@ -245,19 +249,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn every_duration() {
|
||||
assert_eq!(r1080(3).estimate_credits(), 198);
|
||||
assert_eq!(r1080(4).estimate_credits(), 264);
|
||||
assert_eq!(r1080(5).estimate_credits(), 330);
|
||||
assert_eq!(r1080(6).estimate_credits(), 396);
|
||||
assert_eq!(r1080(7).estimate_credits(), 462);
|
||||
assert_eq!(r1080(8).estimate_credits(), 528);
|
||||
assert_eq!(r1080(9).estimate_credits(), 594);
|
||||
assert_eq!(r1080(10).estimate_credits(), 660);
|
||||
assert_eq!(r1080(11).estimate_credits(), 726);
|
||||
assert_eq!(r1080(12).estimate_credits(), 792);
|
||||
assert_eq!(r1080(13).estimate_credits(), 858);
|
||||
assert_eq!(r1080(14).estimate_credits(), 924);
|
||||
assert_eq!(r1080(15).estimate_credits(), 990);
|
||||
assert_eq!(r1080(3).calculate_costs().kinovi_credits, 198);
|
||||
assert_eq!(r1080(4).calculate_costs().kinovi_credits, 264);
|
||||
assert_eq!(r1080(5).calculate_costs().kinovi_credits, 330);
|
||||
assert_eq!(r1080(6).calculate_costs().kinovi_credits, 396);
|
||||
assert_eq!(r1080(7).calculate_costs().kinovi_credits, 462);
|
||||
assert_eq!(r1080(8).calculate_costs().kinovi_credits, 528);
|
||||
assert_eq!(r1080(9).calculate_costs().kinovi_credits, 594);
|
||||
assert_eq!(r1080(10).calculate_costs().kinovi_credits, 660);
|
||||
assert_eq!(r1080(11).calculate_costs().kinovi_credits, 726);
|
||||
assert_eq!(r1080(12).calculate_costs().kinovi_credits, 792);
|
||||
assert_eq!(r1080(13).calculate_costs().kinovi_credits, 858);
|
||||
assert_eq!(r1080(14).calculate_costs().kinovi_credits, 924);
|
||||
assert_eq!(r1080(15).calculate_costs().kinovi_credits, 990);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,30 +272,30 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn batch_1_is_base() {
|
||||
let base = r720(5).estimate_credits();
|
||||
let explicit = make_request(5, None, Some(KinoviHappyHorse1p0BatchCount::One)).estimate_credits();
|
||||
let base = r720(5).calculate_costs().kinovi_credits;
|
||||
let explicit = make_request(5, None, Some(KinoviHappyHorse1p0BatchCount::One)).calculate_costs().kinovi_credits;
|
||||
assert_eq!(base, explicit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_2_doubles() {
|
||||
let base = r720(5).estimate_credits();
|
||||
let batch2 = make_request(5, None, Some(KinoviHappyHorse1p0BatchCount::Two)).estimate_credits();
|
||||
let base = r720(5).calculate_costs().kinovi_credits;
|
||||
let batch2 = make_request(5, None, Some(KinoviHappyHorse1p0BatchCount::Two)).calculate_costs().kinovi_credits;
|
||||
assert_eq!(batch2, base * 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4_quadruples() {
|
||||
let base = r720(5).estimate_credits();
|
||||
let batch4 = make_request(5, None, Some(KinoviHappyHorse1p0BatchCount::Four)).estimate_credits();
|
||||
let base = r720(5).calculate_costs().kinovi_credits;
|
||||
let batch4 = make_request(5, None, Some(KinoviHappyHorse1p0BatchCount::Four)).calculate_costs().kinovi_credits;
|
||||
assert_eq!(batch4, base * 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_multiplier_applies_to_1080p() {
|
||||
let base = r1080(5).estimate_credits();
|
||||
let batch2 = make_request(5, Some(KinoviHappyHorse1p0OutputResolution::TenEightyP), Some(KinoviHappyHorse1p0BatchCount::Two)).estimate_credits();
|
||||
let batch4 = make_request(5, Some(KinoviHappyHorse1p0OutputResolution::TenEightyP), Some(KinoviHappyHorse1p0BatchCount::Four)).estimate_credits();
|
||||
let base = r1080(5).calculate_costs().kinovi_credits;
|
||||
let batch2 = make_request(5, Some(KinoviHappyHorse1p0OutputResolution::TenEightyP), Some(KinoviHappyHorse1p0BatchCount::Two)).calculate_costs().kinovi_credits;
|
||||
let batch4 = make_request(5, Some(KinoviHappyHorse1p0OutputResolution::TenEightyP), Some(KinoviHappyHorse1p0BatchCount::Four)).calculate_costs().kinovi_credits;
|
||||
assert_eq!(batch2, base * 2);
|
||||
assert_eq!(batch4, base * 4);
|
||||
}
|
||||
@@ -305,17 +309,17 @@ mod tests {
|
||||
#[test]
|
||||
fn r1080p_is_exactly_double_720p() {
|
||||
for dur in 3..=15u8 {
|
||||
let c720 = make_request(dur, None, None).estimate_credits();
|
||||
let c1080 = make_request(dur, Some(KinoviHappyHorse1p0OutputResolution::TenEightyP), None).estimate_credits();
|
||||
let c720 = make_request(dur, None, None).calculate_costs().kinovi_credits;
|
||||
let c1080 = make_request(dur, Some(KinoviHappyHorse1p0OutputResolution::TenEightyP), None).calculate_costs().kinovi_credits;
|
||||
assert_eq!(c1080, c720 * 2, "1080p should be 2× 720p at {}s", dur);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cost_scales_with_duration() {
|
||||
let c3 = r720(3).estimate_credits();
|
||||
let c10 = r720(10).estimate_credits();
|
||||
let c15 = r720(15).estimate_credits();
|
||||
let c3 = r720(3).calculate_costs().kinovi_credits;
|
||||
let c10 = r720(10).calculate_costs().kinovi_credits;
|
||||
let c15 = r720(15).calculate_costs().kinovi_credits;
|
||||
assert!(c3 < c10);
|
||||
assert!(c10 < c15);
|
||||
}
|
||||
@@ -323,45 +327,59 @@ mod tests {
|
||||
|
||||
// ── USD cents ──
|
||||
|
||||
mod usd_cents_tests {
|
||||
mod calculate_costs_tests {
|
||||
use super::*;
|
||||
|
||||
/// Both fields together: credits and ceil-rounded USD cents.
|
||||
/// (USD cents are always rounded UP when fractional.)
|
||||
#[test]
|
||||
fn credits_per_dollar_is_193() {
|
||||
assert_eq!(GenerateHappyHorse1p0Request::credits_per_dollar(), 193.0);
|
||||
fn costs_720p_5s() {
|
||||
// 33 credits/s × 5s = 165 credits; 16500/193 = 85.49 → rounds UP to 86¢
|
||||
let costs = r720(5).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 165);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 86);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_720p_5s() {
|
||||
// 165 credits / 193 * 100 = 85.49 → 85¢
|
||||
assert_eq!(r720(5).estimate_cost_in_usd_cents(), 85);
|
||||
fn costs_1080p_5s() {
|
||||
// 66 credits/s × 5s = 330 credits; 33000/193 = 170.98 → 171¢
|
||||
let costs = r1080(5).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 330);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 171);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_1080p_5s() {
|
||||
// 330 credits / 193 * 100 = 170.98 → 171¢
|
||||
assert_eq!(r1080(5).estimate_cost_in_usd_cents(), 171);
|
||||
fn costs_720p_15s() {
|
||||
// 33 credits/s × 15s = 495 credits; 49500/193 = 256.48 → rounds UP to 257¢
|
||||
let costs = r720(15).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 495);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 257);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_720p_15s() {
|
||||
// 495 credits / 193 * 100 = 256.48 → 256¢
|
||||
assert_eq!(r720(15).estimate_cost_in_usd_cents(), 256);
|
||||
fn costs_1080p_15s() {
|
||||
// 66 credits/s × 15s = 990 credits; 99000/193 = 512.95 → 513¢
|
||||
let costs = r1080(15).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 990);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 513);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_1080p_15s() {
|
||||
// 990 credits / 193 * 100 = 512.95 → 513¢
|
||||
assert_eq!(r1080(15).estimate_cost_in_usd_cents(), 513);
|
||||
fn costs_batch_2_720p_5s() {
|
||||
// 165 credits × 2 = 330 credits; 33000/193 = 170.98 → 171¢
|
||||
let costs = make_request(5, None, Some(KinoviHappyHorse1p0BatchCount::Two)).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 330);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 171);
|
||||
}
|
||||
|
||||
/// The deprecated shims return the corresponding struct fields.
|
||||
#[test]
|
||||
fn batch_multiplies_usd_cents() {
|
||||
let base = r720(5).estimate_cost_in_usd_cents();
|
||||
let batch2 = make_request(5, None, Some(KinoviHappyHorse1p0BatchCount::Two)).estimate_cost_in_usd_cents();
|
||||
// Batch 2 should be approximately 2× base (rounding may differ slightly)
|
||||
assert!(batch2 >= base * 2 - 1 && batch2 <= base * 2 + 1,
|
||||
"batch 2 ({}) should be ~2× base ({})", batch2, base);
|
||||
#[allow(deprecated)]
|
||||
fn deprecated_methods_delegate() {
|
||||
let request = r720(5);
|
||||
let costs = request.calculate_costs();
|
||||
assert_eq!(u64::from(request.estimate_credits()), costs.kinovi_credits);
|
||||
assert_eq!(request.estimate_cost_in_usd_cents(), costs.usd_cents_rounded_up);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,7 +387,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_does_not_affect_credits() {
|
||||
let baseline = r720(5).estimate_credits();
|
||||
let baseline = r720(5).calculate_costs().kinovi_credits;
|
||||
|
||||
let ratios = [
|
||||
KinoviHappyHorse1p0AspectRatio::Landscape16x9,
|
||||
@@ -389,7 +407,7 @@ mod tests {
|
||||
start_frame_url: None,
|
||||
};
|
||||
assert_eq!(
|
||||
req.estimate_credits(), baseline,
|
||||
req.calculate_costs().kinovi_credits, baseline,
|
||||
"Aspect ratio {:?} should not change credits from baseline {}", ar, baseline,
|
||||
);
|
||||
}
|
||||
|
||||
+226
-97
@@ -1,5 +1,6 @@
|
||||
use crate::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use crate::error::seedance2pro_error::Seedance2ProError;
|
||||
use crate::generate::cost::KinoviGenerationCost;
|
||||
use crate::requests::kinovi_host::KinoviHost;
|
||||
use crate::requests::workflow_run_task::workflow_run_task::{
|
||||
workflow_run_task, KinoviAspectRatioRaw, KinoviBatchCountRaw,
|
||||
@@ -74,34 +75,67 @@ pub enum KinoviSeedance2p0BatchCount {
|
||||
// Credit package: 22,000 credits for $114 (~192.98 credits/$1, rounded to 193).
|
||||
|
||||
impl GenerateSeedance2p0Request {
|
||||
/// Estimate the credit cost for this generation request.
|
||||
pub fn estimate_credits(&self) -> u32 {
|
||||
let credits_per_second: u32 = match self.output_resolution {
|
||||
/// Calculate the cost of this generation request, in Kinovi credits and
|
||||
/// USD cents (rounded up).
|
||||
///
|
||||
/// Attaching reference VIDEOS adds a per-output-second surcharge (see the
|
||||
/// pricing table below). Reference images and audio are free.
|
||||
pub fn calculate_costs(&self) -> KinoviGenerationCost {
|
||||
let credits_per_second: u64 = match self.output_resolution {
|
||||
Some(KinoviSeedance2p0OutputResolution::FourEightyP) => 15,
|
||||
Some(KinoviSeedance2p0OutputResolution::SevenTwentyP) | None => 40,
|
||||
Some(KinoviSeedance2p0OutputResolution::TenEightyP) => 90,
|
||||
};
|
||||
|
||||
let per_video = u32::from(self.duration_seconds) * credits_per_second;
|
||||
let batch_multiplier: u32 = match self.batch_count {
|
||||
// Video-reference surcharge, billed per second of OUTPUT duration
|
||||
// (not the reference video's duration):
|
||||
//
|
||||
// | Resolution | Surcharge credits/sec |
|
||||
// |------------|-----------------------|
|
||||
// | 480p | 4 |
|
||||
// | 720p | 8 |
|
||||
// | 1080p | 18 |
|
||||
//
|
||||
// NB: Assumed flat per generation regardless of how many reference
|
||||
// videos are attached (Kinovi's pricing page only shows one).
|
||||
let video_reference_surcharge_per_second: u64 = if self.has_video_reference() {
|
||||
match self.output_resolution {
|
||||
Some(KinoviSeedance2p0OutputResolution::FourEightyP) => 4,
|
||||
Some(KinoviSeedance2p0OutputResolution::SevenTwentyP) | None => 8,
|
||||
Some(KinoviSeedance2p0OutputResolution::TenEightyP) => 18,
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let per_video = u64::from(self.duration_seconds)
|
||||
* (credits_per_second + video_reference_surcharge_per_second);
|
||||
let batch_multiplier: u64 = match self.batch_count {
|
||||
None | Some(KinoviSeedance2p0BatchCount::One) => 1,
|
||||
Some(KinoviSeedance2p0BatchCount::Two) => 2,
|
||||
Some(KinoviSeedance2p0BatchCount::Four) => 4,
|
||||
};
|
||||
per_video * batch_multiplier
|
||||
|
||||
KinoviGenerationCost::from_kinovi_credits(per_video * batch_multiplier)
|
||||
}
|
||||
|
||||
/// Credits per dollar for billing conversion.
|
||||
/// 22,000 credits / $114 ≈ 192.98, rounded to 193.
|
||||
fn credits_per_dollar() -> f64 {
|
||||
193.0
|
||||
fn has_video_reference(&self) -> bool {
|
||||
self.reference_video_urls
|
||||
.as_ref()
|
||||
.is_some_and(|urls| !urls.is_empty())
|
||||
}
|
||||
|
||||
/// Estimate the credit cost for this generation request.
|
||||
#[deprecated(note = "Use calculate_costs() instead")]
|
||||
pub fn estimate_credits(&self) -> u32 {
|
||||
self.calculate_costs().kinovi_credits as u32
|
||||
}
|
||||
|
||||
/// Estimate the USD cost in cents for this generation request.
|
||||
/// NB: Rounds UP fractional cents (the historical behavior rounded to nearest).
|
||||
#[deprecated(note = "Use calculate_costs() instead")]
|
||||
pub fn estimate_cost_in_usd_cents(&self) -> u64 {
|
||||
let credits = self.estimate_credits() as f64;
|
||||
let cost = credits / Self::credits_per_dollar() * 100.0;
|
||||
cost.round() as u64
|
||||
self.calculate_costs().usd_cents_rounded_up
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,19 +269,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn every_duration() {
|
||||
assert_eq!(r480(3).estimate_credits(), 45);
|
||||
assert_eq!(r480(4).estimate_credits(), 60);
|
||||
assert_eq!(r480(5).estimate_credits(), 75);
|
||||
assert_eq!(r480(6).estimate_credits(), 90);
|
||||
assert_eq!(r480(7).estimate_credits(), 105);
|
||||
assert_eq!(r480(8).estimate_credits(), 120);
|
||||
assert_eq!(r480(9).estimate_credits(), 135);
|
||||
assert_eq!(r480(10).estimate_credits(), 150);
|
||||
assert_eq!(r480(11).estimate_credits(), 165);
|
||||
assert_eq!(r480(12).estimate_credits(), 180);
|
||||
assert_eq!(r480(13).estimate_credits(), 195);
|
||||
assert_eq!(r480(14).estimate_credits(), 210);
|
||||
assert_eq!(r480(15).estimate_credits(), 225);
|
||||
assert_eq!(r480(3).calculate_costs().kinovi_credits, 45);
|
||||
assert_eq!(r480(4).calculate_costs().kinovi_credits, 60);
|
||||
assert_eq!(r480(5).calculate_costs().kinovi_credits, 75);
|
||||
assert_eq!(r480(6).calculate_costs().kinovi_credits, 90);
|
||||
assert_eq!(r480(7).calculate_costs().kinovi_credits, 105);
|
||||
assert_eq!(r480(8).calculate_costs().kinovi_credits, 120);
|
||||
assert_eq!(r480(9).calculate_costs().kinovi_credits, 135);
|
||||
assert_eq!(r480(10).calculate_costs().kinovi_credits, 150);
|
||||
assert_eq!(r480(11).calculate_costs().kinovi_credits, 165);
|
||||
assert_eq!(r480(12).calculate_costs().kinovi_credits, 180);
|
||||
assert_eq!(r480(13).calculate_costs().kinovi_credits, 195);
|
||||
assert_eq!(r480(14).calculate_costs().kinovi_credits, 210);
|
||||
assert_eq!(r480(15).calculate_costs().kinovi_credits, 225);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -258,25 +292,25 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn every_duration() {
|
||||
assert_eq!(r720(3).estimate_credits(), 120);
|
||||
assert_eq!(r720(4).estimate_credits(), 160);
|
||||
assert_eq!(r720(5).estimate_credits(), 200);
|
||||
assert_eq!(r720(6).estimate_credits(), 240);
|
||||
assert_eq!(r720(7).estimate_credits(), 280);
|
||||
assert_eq!(r720(8).estimate_credits(), 320);
|
||||
assert_eq!(r720(9).estimate_credits(), 360);
|
||||
assert_eq!(r720(10).estimate_credits(), 400);
|
||||
assert_eq!(r720(11).estimate_credits(), 440);
|
||||
assert_eq!(r720(12).estimate_credits(), 480);
|
||||
assert_eq!(r720(13).estimate_credits(), 520);
|
||||
assert_eq!(r720(14).estimate_credits(), 560);
|
||||
assert_eq!(r720(15).estimate_credits(), 600);
|
||||
assert_eq!(r720(3).calculate_costs().kinovi_credits, 120);
|
||||
assert_eq!(r720(4).calculate_costs().kinovi_credits, 160);
|
||||
assert_eq!(r720(5).calculate_costs().kinovi_credits, 200);
|
||||
assert_eq!(r720(6).calculate_costs().kinovi_credits, 240);
|
||||
assert_eq!(r720(7).calculate_costs().kinovi_credits, 280);
|
||||
assert_eq!(r720(8).calculate_costs().kinovi_credits, 320);
|
||||
assert_eq!(r720(9).calculate_costs().kinovi_credits, 360);
|
||||
assert_eq!(r720(10).calculate_costs().kinovi_credits, 400);
|
||||
assert_eq!(r720(11).calculate_costs().kinovi_credits, 440);
|
||||
assert_eq!(r720(12).calculate_costs().kinovi_credits, 480);
|
||||
assert_eq!(r720(13).calculate_costs().kinovi_credits, 520);
|
||||
assert_eq!(r720(14).calculate_costs().kinovi_credits, 560);
|
||||
assert_eq!(r720(15).calculate_costs().kinovi_credits, 600);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_720p_same_as_default() {
|
||||
let default = r720(5).estimate_credits();
|
||||
let explicit = build_request(5, Some(KinoviSeedance2p0OutputResolution::SevenTwentyP), None).estimate_credits();
|
||||
let default = r720(5).calculate_costs().kinovi_credits;
|
||||
let explicit = build_request(5, Some(KinoviSeedance2p0OutputResolution::SevenTwentyP), None).calculate_costs().kinovi_credits;
|
||||
assert_eq!(default, explicit);
|
||||
}
|
||||
}
|
||||
@@ -288,19 +322,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn every_duration() {
|
||||
assert_eq!(r1080(3).estimate_credits(), 270);
|
||||
assert_eq!(r1080(4).estimate_credits(), 360);
|
||||
assert_eq!(r1080(5).estimate_credits(), 450);
|
||||
assert_eq!(r1080(6).estimate_credits(), 540);
|
||||
assert_eq!(r1080(7).estimate_credits(), 630);
|
||||
assert_eq!(r1080(8).estimate_credits(), 720);
|
||||
assert_eq!(r1080(9).estimate_credits(), 810);
|
||||
assert_eq!(r1080(10).estimate_credits(), 900);
|
||||
assert_eq!(r1080(11).estimate_credits(), 990);
|
||||
assert_eq!(r1080(12).estimate_credits(), 1080);
|
||||
assert_eq!(r1080(13).estimate_credits(), 1170);
|
||||
assert_eq!(r1080(14).estimate_credits(), 1260);
|
||||
assert_eq!(r1080(15).estimate_credits(), 1350);
|
||||
assert_eq!(r1080(3).calculate_costs().kinovi_credits, 270);
|
||||
assert_eq!(r1080(4).calculate_costs().kinovi_credits, 360);
|
||||
assert_eq!(r1080(5).calculate_costs().kinovi_credits, 450);
|
||||
assert_eq!(r1080(6).calculate_costs().kinovi_credits, 540);
|
||||
assert_eq!(r1080(7).calculate_costs().kinovi_credits, 630);
|
||||
assert_eq!(r1080(8).calculate_costs().kinovi_credits, 720);
|
||||
assert_eq!(r1080(9).calculate_costs().kinovi_credits, 810);
|
||||
assert_eq!(r1080(10).calculate_costs().kinovi_credits, 900);
|
||||
assert_eq!(r1080(11).calculate_costs().kinovi_credits, 990);
|
||||
assert_eq!(r1080(12).calculate_costs().kinovi_credits, 1080);
|
||||
assert_eq!(r1080(13).calculate_costs().kinovi_credits, 1170);
|
||||
assert_eq!(r1080(14).calculate_costs().kinovi_credits, 1260);
|
||||
assert_eq!(r1080(15).calculate_costs().kinovi_credits, 1350);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,39 +345,39 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn batch_1_is_base() {
|
||||
let base = r720(5).estimate_credits();
|
||||
let explicit = build_request(5, None, Some(KinoviSeedance2p0BatchCount::One)).estimate_credits();
|
||||
let base = r720(5).calculate_costs().kinovi_credits;
|
||||
let explicit = build_request(5, None, Some(KinoviSeedance2p0BatchCount::One)).calculate_costs().kinovi_credits;
|
||||
assert_eq!(base, explicit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_2_doubles() {
|
||||
let base = r720(5).estimate_credits();
|
||||
let batch2 = build_request(5, None, Some(KinoviSeedance2p0BatchCount::Two)).estimate_credits();
|
||||
let base = r720(5).calculate_costs().kinovi_credits;
|
||||
let batch2 = build_request(5, None, Some(KinoviSeedance2p0BatchCount::Two)).calculate_costs().kinovi_credits;
|
||||
assert_eq!(batch2, base * 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4_quadruples() {
|
||||
let base = r720(5).estimate_credits();
|
||||
let batch4 = build_request(5, None, Some(KinoviSeedance2p0BatchCount::Four)).estimate_credits();
|
||||
let base = r720(5).calculate_costs().kinovi_credits;
|
||||
let batch4 = build_request(5, None, Some(KinoviSeedance2p0BatchCount::Four)).calculate_costs().kinovi_credits;
|
||||
assert_eq!(batch4, base * 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_multiplier_applies_to_1080p() {
|
||||
let base = r1080(5).estimate_credits();
|
||||
let batch2 = build_request(5, Some(KinoviSeedance2p0OutputResolution::TenEightyP), Some(KinoviSeedance2p0BatchCount::Two)).estimate_credits();
|
||||
let batch4 = build_request(5, Some(KinoviSeedance2p0OutputResolution::TenEightyP), Some(KinoviSeedance2p0BatchCount::Four)).estimate_credits();
|
||||
let base = r1080(5).calculate_costs().kinovi_credits;
|
||||
let batch2 = build_request(5, Some(KinoviSeedance2p0OutputResolution::TenEightyP), Some(KinoviSeedance2p0BatchCount::Two)).calculate_costs().kinovi_credits;
|
||||
let batch4 = build_request(5, Some(KinoviSeedance2p0OutputResolution::TenEightyP), Some(KinoviSeedance2p0BatchCount::Four)).calculate_costs().kinovi_credits;
|
||||
assert_eq!(batch2, base * 2);
|
||||
assert_eq!(batch4, base * 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_multiplier_applies_to_480p() {
|
||||
let base = r480(5).estimate_credits();
|
||||
let batch2 = build_request(5, Some(KinoviSeedance2p0OutputResolution::FourEightyP), Some(KinoviSeedance2p0BatchCount::Two)).estimate_credits();
|
||||
let batch4 = build_request(5, Some(KinoviSeedance2p0OutputResolution::FourEightyP), Some(KinoviSeedance2p0BatchCount::Four)).estimate_credits();
|
||||
let base = r480(5).calculate_costs().kinovi_credits;
|
||||
let batch2 = build_request(5, Some(KinoviSeedance2p0OutputResolution::FourEightyP), Some(KinoviSeedance2p0BatchCount::Two)).calculate_costs().kinovi_credits;
|
||||
let batch4 = build_request(5, Some(KinoviSeedance2p0OutputResolution::FourEightyP), Some(KinoviSeedance2p0BatchCount::Four)).calculate_costs().kinovi_credits;
|
||||
assert_eq!(batch2, base * 2);
|
||||
assert_eq!(batch4, base * 4);
|
||||
}
|
||||
@@ -356,9 +390,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cost_scales_with_duration() {
|
||||
let c3 = r720(3).estimate_credits();
|
||||
let c10 = r720(10).estimate_credits();
|
||||
let c15 = r720(15).estimate_credits();
|
||||
let c3 = r720(3).calculate_costs().kinovi_credits;
|
||||
let c10 = r720(10).calculate_costs().kinovi_credits;
|
||||
let c15 = r720(15).calculate_costs().kinovi_credits;
|
||||
assert!(c3 < c10);
|
||||
assert!(c10 < c15);
|
||||
}
|
||||
@@ -366,9 +400,9 @@ mod tests {
|
||||
#[test]
|
||||
fn resolution_ordering() {
|
||||
for dur in 3..=15u8 {
|
||||
let c480 = build_request(dur, Some(KinoviSeedance2p0OutputResolution::FourEightyP), None).estimate_credits();
|
||||
let c720 = build_request(dur, None, None).estimate_credits();
|
||||
let c1080 = build_request(dur, Some(KinoviSeedance2p0OutputResolution::TenEightyP), None).estimate_credits();
|
||||
let c480 = build_request(dur, Some(KinoviSeedance2p0OutputResolution::FourEightyP), None).calculate_costs().kinovi_credits;
|
||||
let c720 = build_request(dur, None, None).calculate_costs().kinovi_credits;
|
||||
let c1080 = build_request(dur, Some(KinoviSeedance2p0OutputResolution::TenEightyP), None).calculate_costs().kinovi_credits;
|
||||
assert!(c480 < c720, "480p should be cheaper than 720p at {}s", dur);
|
||||
assert!(c720 < c1080, "720p should be cheaper than 1080p at {}s", dur);
|
||||
}
|
||||
@@ -377,50 +411,145 @@ mod tests {
|
||||
|
||||
// ── USD cents ──
|
||||
|
||||
mod usd_cents_tests {
|
||||
mod calculate_costs_tests {
|
||||
use super::*;
|
||||
|
||||
/// Both fields together: credits and ceil-rounded USD cents.
|
||||
/// (USD cents are always rounded UP when fractional.)
|
||||
#[test]
|
||||
fn credits_per_dollar_is_193() {
|
||||
assert_eq!(GenerateSeedance2p0Request::credits_per_dollar(), 193.0);
|
||||
fn costs_480p_5s() {
|
||||
// 15 credits/s × 5s = 75 credits; 7500/193 = 38.86 → 39¢
|
||||
let costs = r480(5).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 75);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 39);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_720p_5s() {
|
||||
// 200 credits / 193 * 100 = 103.63 → 104¢
|
||||
assert_eq!(r720(5).estimate_cost_in_usd_cents(), 104);
|
||||
fn costs_720p_5s() {
|
||||
// 40 credits/s × 5s = 200 credits; 20000/193 = 103.63 → 104¢
|
||||
let costs = r720(5).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 200);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 104);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_480p_5s() {
|
||||
// 75 credits / 193 * 100 = 38.86 → 39¢
|
||||
assert_eq!(r480(5).estimate_cost_in_usd_cents(), 39);
|
||||
fn costs_1080p_5s() {
|
||||
// 90 credits/s × 5s = 450 credits; 45000/193 = 233.16 → rounds UP to 234¢
|
||||
let costs = r1080(5).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 450);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 234);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_1080p_5s() {
|
||||
// 450 credits / 193 * 100 = 233.16 → 233¢
|
||||
assert_eq!(r1080(5).estimate_cost_in_usd_cents(), 233);
|
||||
fn costs_720p_15s() {
|
||||
// 40 credits/s × 15s = 600 credits; 60000/193 = 310.88 → 311¢
|
||||
let costs = r720(15).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 600);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 311);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_720p_15s() {
|
||||
// 600 credits / 193 * 100 = 310.88 → 311¢
|
||||
assert_eq!(r720(15).estimate_cost_in_usd_cents(), 311);
|
||||
fn costs_1080p_15s() {
|
||||
// 90 credits/s × 15s = 1350 credits; 135000/193 = 699.48 → rounds UP to 700¢
|
||||
let costs = r1080(15).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 1350);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 700);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_1080p_15s() {
|
||||
// 1350 credits / 193 * 100 = 699.48 → 699¢
|
||||
assert_eq!(r1080(15).estimate_cost_in_usd_cents(), 699);
|
||||
fn costs_batch_4_720p_5s() {
|
||||
// 200 credits × 4 = 800 credits; 80000/193 = 414.51 → 415¢
|
||||
let costs = build_request(5, None, Some(KinoviSeedance2p0BatchCount::Four)).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 800);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 415);
|
||||
}
|
||||
|
||||
/// The deprecated shims return the corresponding struct fields.
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn deprecated_methods_delegate() {
|
||||
let request = r720(5);
|
||||
let costs = request.calculate_costs();
|
||||
assert_eq!(u64::from(request.estimate_credits()), costs.kinovi_credits);
|
||||
assert_eq!(request.estimate_cost_in_usd_cents(), costs.usd_cents_rounded_up);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Video-reference surcharge ──
|
||||
//
|
||||
// Per Kinovi's pricing page, attaching a reference video adds a
|
||||
// per-output-second surcharge (480p: +4/s, 720p: +8/s, 1080p: +18/s).
|
||||
|
||||
mod video_reference_surcharge_tests {
|
||||
use super::*;
|
||||
|
||||
fn with_video_ref(mut request: GenerateSeedance2p0Request) -> GenerateSeedance2p0Request {
|
||||
request.reference_video_urls = Some(vec!["https://example.com/ref.mp4".to_string()]);
|
||||
request
|
||||
}
|
||||
|
||||
/// The full base + surcharge table from Kinovi's pricing page
|
||||
/// ("With Video Uploads", 10 sec video ref).
|
||||
#[test]
|
||||
fn kinovi_pricing_table_with_video_reference() {
|
||||
let cases: &[(fn(u8) -> GenerateSeedance2p0Request, u8, u64)] = &[
|
||||
// 480p: base + 4/s
|
||||
(r480, 4, 60 + 16),
|
||||
(r480, 5, 75 + 20),
|
||||
(r480, 10, 150 + 40),
|
||||
(r480, 15, 225 + 60),
|
||||
// 720p: base + 8/s
|
||||
(r720, 4, 160 + 32),
|
||||
(r720, 5, 200 + 40),
|
||||
(r720, 10, 400 + 80),
|
||||
(r720, 15, 600 + 120),
|
||||
// 1080p: base + 18/s
|
||||
(r1080, 4, 360 + 72),
|
||||
(r1080, 5, 450 + 90),
|
||||
(r1080, 10, 900 + 180),
|
||||
(r1080, 15, 1350 + 270),
|
||||
];
|
||||
|
||||
for (make, duration, expected_credits) in cases {
|
||||
let costs = with_video_ref(make(*duration)).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, *expected_credits,
|
||||
"duration {duration}s should cost {expected_credits} credits with a video reference");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_multiplies_usd_cents() {
|
||||
let base = r720(5).estimate_cost_in_usd_cents();
|
||||
let batch2 = build_request(5, None, Some(KinoviSeedance2p0BatchCount::Two)).estimate_cost_in_usd_cents();
|
||||
assert!(batch2 >= base * 2 - 1 && batch2 <= base * 2 + 1,
|
||||
"batch 2 ({}) should be ~2× base ({})", batch2, base);
|
||||
fn surcharge_includes_usd_cents() {
|
||||
// 720p 5s + video ref = 240 credits; 24000/193 = 124.35 → 125¢
|
||||
let costs = with_video_ref(r720(5)).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 240);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 125);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_video_reference_list_has_no_surcharge() {
|
||||
let mut request = r720(5);
|
||||
request.reference_video_urls = Some(vec![]);
|
||||
assert_eq!(request.calculate_costs().kinovi_credits, 200);
|
||||
}
|
||||
|
||||
/// Surcharge is flat per generation regardless of how many reference
|
||||
/// videos are attached (assumption — Kinovi's page only shows one).
|
||||
#[test]
|
||||
fn multiple_video_references_charge_once() {
|
||||
let mut request = r720(5);
|
||||
request.reference_video_urls = Some(vec![
|
||||
"https://example.com/a.mp4".to_string(),
|
||||
"https://example.com/b.mp4".to_string(),
|
||||
]);
|
||||
assert_eq!(request.calculate_costs().kinovi_credits, 240);
|
||||
}
|
||||
|
||||
/// The surcharge applies per generated video, so batches multiply it.
|
||||
#[test]
|
||||
fn batch_multiplies_surcharge() {
|
||||
let request = with_video_ref(build_request(5, None, Some(KinoviSeedance2p0BatchCount::Two)));
|
||||
// (200 base + 40 surcharge) × 2 = 480 credits
|
||||
assert_eq!(request.calculate_costs().kinovi_credits, 480);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -428,7 +557,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_does_not_affect_credits() {
|
||||
let baseline = r720(5).estimate_credits();
|
||||
let baseline = r720(5).calculate_costs().kinovi_credits;
|
||||
|
||||
let ratios = [
|
||||
KinoviSeedance2p0AspectRatio::Landscape16x9,
|
||||
@@ -455,7 +584,7 @@ mod tests {
|
||||
use_face_blur_hack: None,
|
||||
};
|
||||
assert_eq!(
|
||||
req.estimate_credits(), baseline,
|
||||
req.calculate_costs().kinovi_credits, baseline,
|
||||
"Aspect ratio {:?} should not change credits from baseline {}", ar, baseline,
|
||||
);
|
||||
}
|
||||
|
||||
+198
-77
@@ -1,5 +1,6 @@
|
||||
use crate::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use crate::error::seedance2pro_error::Seedance2ProError;
|
||||
use crate::generate::cost::KinoviGenerationCost;
|
||||
use crate::requests::kinovi_host::KinoviHost;
|
||||
use crate::requests::workflow_run_task::workflow_run_task::{
|
||||
workflow_run_task, KinoviAspectRatioRaw, KinoviBatchCountRaw,
|
||||
@@ -72,33 +73,64 @@ pub enum KinoviSeedance2p0FastBatchCount {
|
||||
// Credit package: 22,000 credits for $114 (~192.98 credits/$1, rounded to 193).
|
||||
|
||||
impl GenerateSeedance2p0FastRequest {
|
||||
/// Estimate the credit cost for this generation request.
|
||||
pub fn estimate_credits(&self) -> u32 {
|
||||
let credits_per_second: u32 = match self.output_resolution {
|
||||
/// Calculate the cost of this generation request, in Kinovi credits and
|
||||
/// USD cents (rounded up).
|
||||
///
|
||||
/// Attaching reference VIDEOS adds a per-output-second surcharge (see the
|
||||
/// pricing table below). Reference images and audio are free.
|
||||
pub fn calculate_costs(&self) -> KinoviGenerationCost {
|
||||
let credits_per_second: u64 = match self.output_resolution {
|
||||
Some(KinoviSeedance2p0FastOutputResolution::FourEightyP) => 14,
|
||||
Some(KinoviSeedance2p0FastOutputResolution::SevenTwentyP) | None => 28,
|
||||
};
|
||||
|
||||
let per_video = u32::from(self.duration_seconds) * credits_per_second;
|
||||
let batch_multiplier: u32 = match self.batch_count {
|
||||
// Video-reference surcharge, billed per second of OUTPUT duration
|
||||
// (not the reference video's duration):
|
||||
//
|
||||
// | Resolution | Surcharge credits/sec |
|
||||
// |------------|-----------------------|
|
||||
// | 480p | 4 |
|
||||
// | 720p | 6 |
|
||||
//
|
||||
// NB: Assumed flat per generation regardless of how many reference
|
||||
// videos are attached (Kinovi's pricing page only shows one).
|
||||
let video_reference_surcharge_per_second: u64 = if self.has_video_reference() {
|
||||
match self.output_resolution {
|
||||
Some(KinoviSeedance2p0FastOutputResolution::FourEightyP) => 4,
|
||||
Some(KinoviSeedance2p0FastOutputResolution::SevenTwentyP) | None => 6,
|
||||
}
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
let per_video = u64::from(self.duration_seconds)
|
||||
* (credits_per_second + video_reference_surcharge_per_second);
|
||||
let batch_multiplier: u64 = match self.batch_count {
|
||||
None | Some(KinoviSeedance2p0FastBatchCount::One) => 1,
|
||||
Some(KinoviSeedance2p0FastBatchCount::Two) => 2,
|
||||
Some(KinoviSeedance2p0FastBatchCount::Four) => 4,
|
||||
};
|
||||
per_video * batch_multiplier
|
||||
|
||||
KinoviGenerationCost::from_kinovi_credits(per_video * batch_multiplier)
|
||||
}
|
||||
|
||||
/// Credits per dollar for billing conversion.
|
||||
/// 22,000 credits / $114 ≈ 192.98, rounded to 193.
|
||||
fn credits_per_dollar() -> f64 {
|
||||
193.0
|
||||
fn has_video_reference(&self) -> bool {
|
||||
self.reference_video_urls
|
||||
.as_ref()
|
||||
.is_some_and(|urls| !urls.is_empty())
|
||||
}
|
||||
|
||||
/// Estimate the credit cost for this generation request.
|
||||
#[deprecated(note = "Use calculate_costs() instead")]
|
||||
pub fn estimate_credits(&self) -> u32 {
|
||||
self.calculate_costs().kinovi_credits as u32
|
||||
}
|
||||
|
||||
/// Estimate the USD cost in cents for this generation request.
|
||||
/// NB: Rounds UP fractional cents (the historical behavior rounded to nearest).
|
||||
#[deprecated(note = "Use calculate_costs() instead")]
|
||||
pub fn estimate_cost_in_usd_cents(&self) -> u64 {
|
||||
let credits = self.estimate_credits() as f64;
|
||||
let cost = credits / Self::credits_per_dollar() * 100.0;
|
||||
cost.round() as u64
|
||||
self.calculate_costs().usd_cents_rounded_up
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,19 +259,19 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn every_duration() {
|
||||
assert_eq!(r480(3).estimate_credits(), 42);
|
||||
assert_eq!(r480(4).estimate_credits(), 56);
|
||||
assert_eq!(r480(5).estimate_credits(), 70);
|
||||
assert_eq!(r480(6).estimate_credits(), 84);
|
||||
assert_eq!(r480(7).estimate_credits(), 98);
|
||||
assert_eq!(r480(8).estimate_credits(), 112);
|
||||
assert_eq!(r480(9).estimate_credits(), 126);
|
||||
assert_eq!(r480(10).estimate_credits(), 140);
|
||||
assert_eq!(r480(11).estimate_credits(), 154);
|
||||
assert_eq!(r480(12).estimate_credits(), 168);
|
||||
assert_eq!(r480(13).estimate_credits(), 182);
|
||||
assert_eq!(r480(14).estimate_credits(), 196);
|
||||
assert_eq!(r480(15).estimate_credits(), 210);
|
||||
assert_eq!(r480(3).calculate_costs().kinovi_credits, 42);
|
||||
assert_eq!(r480(4).calculate_costs().kinovi_credits, 56);
|
||||
assert_eq!(r480(5).calculate_costs().kinovi_credits, 70);
|
||||
assert_eq!(r480(6).calculate_costs().kinovi_credits, 84);
|
||||
assert_eq!(r480(7).calculate_costs().kinovi_credits, 98);
|
||||
assert_eq!(r480(8).calculate_costs().kinovi_credits, 112);
|
||||
assert_eq!(r480(9).calculate_costs().kinovi_credits, 126);
|
||||
assert_eq!(r480(10).calculate_costs().kinovi_credits, 140);
|
||||
assert_eq!(r480(11).calculate_costs().kinovi_credits, 154);
|
||||
assert_eq!(r480(12).calculate_costs().kinovi_credits, 168);
|
||||
assert_eq!(r480(13).calculate_costs().kinovi_credits, 182);
|
||||
assert_eq!(r480(14).calculate_costs().kinovi_credits, 196);
|
||||
assert_eq!(r480(15).calculate_costs().kinovi_credits, 210);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,25 +282,25 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn every_duration() {
|
||||
assert_eq!(r720(3).estimate_credits(), 84);
|
||||
assert_eq!(r720(4).estimate_credits(), 112);
|
||||
assert_eq!(r720(5).estimate_credits(), 140);
|
||||
assert_eq!(r720(6).estimate_credits(), 168);
|
||||
assert_eq!(r720(7).estimate_credits(), 196);
|
||||
assert_eq!(r720(8).estimate_credits(), 224);
|
||||
assert_eq!(r720(9).estimate_credits(), 252);
|
||||
assert_eq!(r720(10).estimate_credits(), 280);
|
||||
assert_eq!(r720(11).estimate_credits(), 308);
|
||||
assert_eq!(r720(12).estimate_credits(), 336);
|
||||
assert_eq!(r720(13).estimate_credits(), 364);
|
||||
assert_eq!(r720(14).estimate_credits(), 392);
|
||||
assert_eq!(r720(15).estimate_credits(), 420);
|
||||
assert_eq!(r720(3).calculate_costs().kinovi_credits, 84);
|
||||
assert_eq!(r720(4).calculate_costs().kinovi_credits, 112);
|
||||
assert_eq!(r720(5).calculate_costs().kinovi_credits, 140);
|
||||
assert_eq!(r720(6).calculate_costs().kinovi_credits, 168);
|
||||
assert_eq!(r720(7).calculate_costs().kinovi_credits, 196);
|
||||
assert_eq!(r720(8).calculate_costs().kinovi_credits, 224);
|
||||
assert_eq!(r720(9).calculate_costs().kinovi_credits, 252);
|
||||
assert_eq!(r720(10).calculate_costs().kinovi_credits, 280);
|
||||
assert_eq!(r720(11).calculate_costs().kinovi_credits, 308);
|
||||
assert_eq!(r720(12).calculate_costs().kinovi_credits, 336);
|
||||
assert_eq!(r720(13).calculate_costs().kinovi_credits, 364);
|
||||
assert_eq!(r720(14).calculate_costs().kinovi_credits, 392);
|
||||
assert_eq!(r720(15).calculate_costs().kinovi_credits, 420);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn explicit_720p_same_as_default() {
|
||||
let default = r720(5).estimate_credits();
|
||||
let explicit = build_request(5, Some(KinoviSeedance2p0FastOutputResolution::SevenTwentyP), None).estimate_credits();
|
||||
let default = r720(5).calculate_costs().kinovi_credits;
|
||||
let explicit = build_request(5, Some(KinoviSeedance2p0FastOutputResolution::SevenTwentyP), None).calculate_costs().kinovi_credits;
|
||||
assert_eq!(default, explicit);
|
||||
}
|
||||
}
|
||||
@@ -280,30 +312,30 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn batch_1_is_base() {
|
||||
let base = r720(5).estimate_credits();
|
||||
let explicit = build_request(5, None, Some(KinoviSeedance2p0FastBatchCount::One)).estimate_credits();
|
||||
let base = r720(5).calculate_costs().kinovi_credits;
|
||||
let explicit = build_request(5, None, Some(KinoviSeedance2p0FastBatchCount::One)).calculate_costs().kinovi_credits;
|
||||
assert_eq!(base, explicit);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_2_doubles() {
|
||||
let base = r720(5).estimate_credits();
|
||||
let batch2 = build_request(5, None, Some(KinoviSeedance2p0FastBatchCount::Two)).estimate_credits();
|
||||
let base = r720(5).calculate_costs().kinovi_credits;
|
||||
let batch2 = build_request(5, None, Some(KinoviSeedance2p0FastBatchCount::Two)).calculate_costs().kinovi_credits;
|
||||
assert_eq!(batch2, base * 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_4_quadruples() {
|
||||
let base = r720(5).estimate_credits();
|
||||
let batch4 = build_request(5, None, Some(KinoviSeedance2p0FastBatchCount::Four)).estimate_credits();
|
||||
let base = r720(5).calculate_costs().kinovi_credits;
|
||||
let batch4 = build_request(5, None, Some(KinoviSeedance2p0FastBatchCount::Four)).calculate_costs().kinovi_credits;
|
||||
assert_eq!(batch4, base * 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_multiplier_applies_to_480p() {
|
||||
let base = r480(5).estimate_credits();
|
||||
let batch2 = build_request(5, Some(KinoviSeedance2p0FastOutputResolution::FourEightyP), Some(KinoviSeedance2p0FastBatchCount::Two)).estimate_credits();
|
||||
let batch4 = build_request(5, Some(KinoviSeedance2p0FastOutputResolution::FourEightyP), Some(KinoviSeedance2p0FastBatchCount::Four)).estimate_credits();
|
||||
let base = r480(5).calculate_costs().kinovi_credits;
|
||||
let batch2 = build_request(5, Some(KinoviSeedance2p0FastOutputResolution::FourEightyP), Some(KinoviSeedance2p0FastBatchCount::Two)).calculate_costs().kinovi_credits;
|
||||
let batch4 = build_request(5, Some(KinoviSeedance2p0FastOutputResolution::FourEightyP), Some(KinoviSeedance2p0FastBatchCount::Four)).calculate_costs().kinovi_credits;
|
||||
assert_eq!(batch2, base * 2);
|
||||
assert_eq!(batch4, base * 4);
|
||||
}
|
||||
@@ -316,9 +348,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cost_scales_with_duration() {
|
||||
let c3 = r720(3).estimate_credits();
|
||||
let c10 = r720(10).estimate_credits();
|
||||
let c15 = r720(15).estimate_credits();
|
||||
let c3 = r720(3).calculate_costs().kinovi_credits;
|
||||
let c10 = r720(10).calculate_costs().kinovi_credits;
|
||||
let c15 = r720(15).calculate_costs().kinovi_credits;
|
||||
assert!(c3 < c10);
|
||||
assert!(c10 < c15);
|
||||
}
|
||||
@@ -326,8 +358,8 @@ mod tests {
|
||||
#[test]
|
||||
fn resolution_ordering() {
|
||||
for dur in 3..=15u8 {
|
||||
let c480 = build_request(dur, Some(KinoviSeedance2p0FastOutputResolution::FourEightyP), None).estimate_credits();
|
||||
let c720 = build_request(dur, None, None).estimate_credits();
|
||||
let c480 = build_request(dur, Some(KinoviSeedance2p0FastOutputResolution::FourEightyP), None).calculate_costs().kinovi_credits;
|
||||
let c720 = build_request(dur, None, None).calculate_costs().kinovi_credits;
|
||||
assert!(c480 < c720, "480p should be cheaper than 720p at {}s", dur);
|
||||
}
|
||||
}
|
||||
@@ -335,44 +367,133 @@ mod tests {
|
||||
|
||||
// ── USD cents ──
|
||||
|
||||
mod usd_cents_tests {
|
||||
mod calculate_costs_tests {
|
||||
use super::*;
|
||||
|
||||
/// Both fields together: credits and ceil-rounded USD cents.
|
||||
/// (USD cents are always rounded UP when fractional.)
|
||||
#[test]
|
||||
fn credits_per_dollar_is_193() {
|
||||
assert_eq!(GenerateSeedance2p0FastRequest::credits_per_dollar(), 193.0);
|
||||
fn costs_480p_5s() {
|
||||
// 14 credits/s × 5s = 70 credits; 7000/193 = 36.27 → rounds UP to 37¢
|
||||
let costs = r480(5).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 70);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 37);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_720p_5s() {
|
||||
// 140 credits / 193 * 100 = 72.54 → 73¢
|
||||
assert_eq!(r720(5).estimate_cost_in_usd_cents(), 73);
|
||||
fn costs_720p_5s() {
|
||||
// 28 credits/s × 5s = 140 credits; 14000/193 = 72.54 → 73¢
|
||||
let costs = r720(5).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 140);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 73);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_480p_5s() {
|
||||
// 70 credits / 193 * 100 = 36.27 → 36¢
|
||||
assert_eq!(r480(5).estimate_cost_in_usd_cents(), 36);
|
||||
fn costs_720p_15s() {
|
||||
// 28 credits/s × 15s = 420 credits; 42000/193 = 217.62 → 218¢
|
||||
let costs = r720(15).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 420);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 218);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_720p_15s() {
|
||||
// 420 credits / 193 * 100 = 217.62 → 218¢
|
||||
assert_eq!(r720(15).estimate_cost_in_usd_cents(), 218);
|
||||
fn costs_480p_15s() {
|
||||
// 14 credits/s × 15s = 210 credits; 21000/193 = 108.81 → 109¢
|
||||
let costs = r480(15).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 210);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 109);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_480p_15s() {
|
||||
// 210 credits / 193 * 100 = 108.81 → 109¢
|
||||
assert_eq!(r480(15).estimate_cost_in_usd_cents(), 109);
|
||||
fn costs_batch_2_720p_5s() {
|
||||
// 140 credits × 2 = 280 credits; 28000/193 = 145.07 → 146¢
|
||||
let costs = build_request(5, None, Some(KinoviSeedance2p0FastBatchCount::Two)).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 280);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 146);
|
||||
}
|
||||
|
||||
/// The deprecated shims return the corresponding struct fields.
|
||||
#[test]
|
||||
#[allow(deprecated)]
|
||||
fn deprecated_methods_delegate() {
|
||||
let request = r720(5);
|
||||
let costs = request.calculate_costs();
|
||||
assert_eq!(u64::from(request.estimate_credits()), costs.kinovi_credits);
|
||||
assert_eq!(request.estimate_cost_in_usd_cents(), costs.usd_cents_rounded_up);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Video-reference surcharge ──
|
||||
//
|
||||
// Per Kinovi's pricing page ("Seedance 2.0 Fast With Video Ref"),
|
||||
// attaching a reference video adds a per-output-second surcharge
|
||||
// (480p: +4/s, 720p: +6/s).
|
||||
|
||||
mod video_reference_surcharge_tests {
|
||||
use super::*;
|
||||
|
||||
fn with_video_ref(mut request: GenerateSeedance2p0FastRequest) -> GenerateSeedance2p0FastRequest {
|
||||
request.reference_video_urls = Some(vec!["https://example.com/ref.mp4".to_string()]);
|
||||
request
|
||||
}
|
||||
|
||||
/// The full base + surcharge table from Kinovi's pricing page
|
||||
/// ("Seedance 2.0 Fast With Video Ref", 10 sec video ref).
|
||||
#[test]
|
||||
fn kinovi_pricing_table_with_video_reference() {
|
||||
let cases: &[(fn(u8) -> GenerateSeedance2p0FastRequest, u8, u64)] = &[
|
||||
// 480p: base + 4/s
|
||||
(r480, 4, 56 + 16),
|
||||
(r480, 5, 70 + 20),
|
||||
(r480, 10, 140 + 40),
|
||||
(r480, 15, 210 + 60),
|
||||
// 720p: base + 6/s
|
||||
(r720, 4, 112 + 24),
|
||||
(r720, 5, 140 + 30),
|
||||
(r720, 10, 280 + 60),
|
||||
(r720, 15, 420 + 90),
|
||||
];
|
||||
|
||||
for (make, duration, expected_credits) in cases {
|
||||
let costs = with_video_ref(make(*duration)).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, *expected_credits,
|
||||
"duration {duration}s should cost {expected_credits} credits with a video reference");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_multiplies_usd_cents() {
|
||||
let base = r720(5).estimate_cost_in_usd_cents();
|
||||
let batch2 = build_request(5, None, Some(KinoviSeedance2p0FastBatchCount::Two)).estimate_cost_in_usd_cents();
|
||||
assert!(batch2 >= base * 2 - 1 && batch2 <= base * 2 + 1,
|
||||
"batch 2 ({}) should be ~2× base ({})", batch2, base);
|
||||
fn surcharge_includes_usd_cents() {
|
||||
// 720p 5s + video ref = 170 credits; 17000/193 = 88.08 → 89¢
|
||||
let costs = with_video_ref(r720(5)).calculate_costs();
|
||||
assert_eq!(costs.kinovi_credits, 170);
|
||||
assert_eq!(costs.usd_cents_rounded_up, 89);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_video_reference_list_has_no_surcharge() {
|
||||
let mut request = r720(5);
|
||||
request.reference_video_urls = Some(vec![]);
|
||||
assert_eq!(request.calculate_costs().kinovi_credits, 140);
|
||||
}
|
||||
|
||||
/// Surcharge is flat per generation regardless of how many reference
|
||||
/// videos are attached (assumption — Kinovi's page only shows one).
|
||||
#[test]
|
||||
fn multiple_video_references_charge_once() {
|
||||
let mut request = r720(5);
|
||||
request.reference_video_urls = Some(vec![
|
||||
"https://example.com/a.mp4".to_string(),
|
||||
"https://example.com/b.mp4".to_string(),
|
||||
]);
|
||||
assert_eq!(request.calculate_costs().kinovi_credits, 170);
|
||||
}
|
||||
|
||||
/// The surcharge applies per generated video, so batches multiply it.
|
||||
#[test]
|
||||
fn batch_multiplies_surcharge() {
|
||||
let request = with_video_ref(build_request(5, None, Some(KinoviSeedance2p0FastBatchCount::Two)));
|
||||
// (140 base + 30 surcharge) × 2 = 340 credits
|
||||
assert_eq!(request.calculate_costs().kinovi_credits, 340);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -380,7 +501,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn aspect_ratio_does_not_affect_credits() {
|
||||
let baseline = r720(5).estimate_credits();
|
||||
let baseline = r720(5).calculate_costs().kinovi_credits;
|
||||
|
||||
let ratios = [
|
||||
KinoviSeedance2p0FastAspectRatio::Landscape16x9,
|
||||
@@ -407,7 +528,7 @@ mod tests {
|
||||
use_face_blur_hack: None,
|
||||
};
|
||||
assert_eq!(
|
||||
req.estimate_credits(), baseline,
|
||||
req.calculate_costs().kinovi_credits, baseline,
|
||||
"Aspect ratio {:?} should not change credits from baseline {}", ar, baseline,
|
||||
);
|
||||
}
|
||||
|
||||
+37
-26
@@ -1,3 +1,4 @@
|
||||
use crate::generate::cost::KinoviGenerationCost;
|
||||
use crate::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use crate::error::seedance2pro_bad_request_api_error::Seedance2ProBadRequestApiError;
|
||||
use crate::error::seedance2pro_client_error::Seedance2ProClientError;
|
||||
@@ -22,7 +23,6 @@ const CREDITS_PER_MIDJOURNEY_TASK: u32 = 12;
|
||||
/// Midjourney image generation flow.
|
||||
///
|
||||
/// 22,000 credits / $114 = 192.98 ≈ 193 credits per dollar.
|
||||
const CREDITS_PER_DOLLAR: f64 = 193.0;
|
||||
|
||||
/// Kinovi only currently exposes a single Midjourney resolution preset.
|
||||
const MIDJOURNEY_RESOLUTION: &str = "1k";
|
||||
@@ -121,19 +121,27 @@ impl std::fmt::Debug for GenerateImageArgs<'_> {
|
||||
}
|
||||
|
||||
impl KinoviGenerateImageRequest {
|
||||
/// Estimates the Kinovi credit cost for this request.
|
||||
/// Calculate the cost of this request, in Kinovi credits and USD cents
|
||||
/// (rounded up).
|
||||
///
|
||||
/// Pricing for Midjourney is flat: 12 credits per task, regardless of
|
||||
/// model or aspect ratio. Batches multiply the cost by `batch_count`.
|
||||
pub fn estimate_credits(&self) -> u32 {
|
||||
CREDITS_PER_MIDJOURNEY_TASK * u32::from(self.batch_count.as_u8())
|
||||
pub fn calculate_costs(&self) -> KinoviGenerationCost {
|
||||
let credits = u64::from(CREDITS_PER_MIDJOURNEY_TASK) * u64::from(self.batch_count.as_u8());
|
||||
KinoviGenerationCost::from_kinovi_credits(credits)
|
||||
}
|
||||
|
||||
/// Estimates the dollar cost (in USD cents, rounded to the nearest cent).
|
||||
/// Estimates the Kinovi credit cost for this request.
|
||||
#[deprecated(note = "Use calculate_costs() instead")]
|
||||
pub fn estimate_credits(&self) -> u32 {
|
||||
self.calculate_costs().kinovi_credits as u32
|
||||
}
|
||||
|
||||
/// Estimates the dollar cost in USD cents.
|
||||
/// NB: Rounds UP fractional cents (the historical behavior rounded to nearest).
|
||||
#[deprecated(note = "Use calculate_costs() instead")]
|
||||
pub fn estimate_cost_in_usd_cents(&self) -> u64 {
|
||||
let credits = self.estimate_credits() as f64;
|
||||
let cost = credits / CREDITS_PER_DOLLAR * 100.0;
|
||||
cost.round() as u64
|
||||
self.calculate_costs().usd_cents_rounded_up
|
||||
}
|
||||
}
|
||||
|
||||
@@ -348,50 +356,50 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn batch_one_is_twelve_credits() {
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::One).estimate_credits(), 12);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::One).calculate_costs().kinovi_credits, 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_two_is_twentyfour_credits() {
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Two).estimate_credits(), 24);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Two).calculate_costs().kinovi_credits, 24);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_four_is_fortyeight_credits() {
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Four).estimate_credits(), 48);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Four).calculate_costs().kinovi_credits, 48);
|
||||
}
|
||||
|
||||
/// Pricing is identical across all three Midjourney models.
|
||||
#[test]
|
||||
fn pricing_is_model_agnostic() {
|
||||
let baseline = make_request(KinoviMidjourneyBatchCount::One).estimate_credits();
|
||||
let baseline = make_request(KinoviMidjourneyBatchCount::One).calculate_costs().kinovi_credits;
|
||||
for model in [
|
||||
KinoviMidjourneyModel::V7,
|
||||
KinoviMidjourneyModel::V7Niji,
|
||||
KinoviMidjourneyModel::V8,
|
||||
] {
|
||||
let req = KinoviGenerateImageRequest { model, ..make_request(KinoviMidjourneyBatchCount::One) };
|
||||
assert_eq!(req.estimate_credits(), baseline, "model={:?}", model);
|
||||
assert_eq!(req.calculate_costs().kinovi_credits, baseline, "model={:?}", model);
|
||||
}
|
||||
}
|
||||
|
||||
/// Pricing does not vary by aspect ratio.
|
||||
#[test]
|
||||
fn pricing_is_aspect_ratio_agnostic() {
|
||||
let baseline = make_request(KinoviMidjourneyBatchCount::One).estimate_credits();
|
||||
let baseline = make_request(KinoviMidjourneyBatchCount::One).calculate_costs().kinovi_credits;
|
||||
for ar in ["1:1", "16:9", "9:16", "21:9", "9:21", "4:3", "3:4", "5:4", "4:5", "3:2", "2:3"] {
|
||||
let req = KinoviGenerateImageRequest {
|
||||
aspect_ratio: ar.to_string(),
|
||||
..make_request(KinoviMidjourneyBatchCount::One)
|
||||
};
|
||||
assert_eq!(req.estimate_credits(), baseline, "aspect_ratio={:?}", ar);
|
||||
assert_eq!(req.calculate_costs().kinovi_credits, baseline, "aspect_ratio={:?}", ar);
|
||||
}
|
||||
}
|
||||
|
||||
/// Pricing does not vary with the Midjourney style knobs.
|
||||
#[test]
|
||||
fn pricing_is_independent_of_style_knobs() {
|
||||
let baseline = make_request(KinoviMidjourneyBatchCount::One).estimate_credits();
|
||||
let baseline = make_request(KinoviMidjourneyBatchCount::One).calculate_costs().kinovi_credits;
|
||||
let req = KinoviGenerateImageRequest {
|
||||
stylize: Some(1000),
|
||||
weird: Some(3000),
|
||||
@@ -401,7 +409,7 @@ mod tests {
|
||||
negative_prompt: Some("ugly, blurry".to_string()),
|
||||
..make_request(KinoviMidjourneyBatchCount::One)
|
||||
};
|
||||
assert_eq!(req.estimate_credits(), baseline);
|
||||
assert_eq!(req.calculate_costs().kinovi_credits, baseline);
|
||||
}
|
||||
|
||||
// ── USD cents conversion ──
|
||||
@@ -409,19 +417,19 @@ mod tests {
|
||||
#[test]
|
||||
fn usd_cents_batch_one() {
|
||||
// 12 / 193 × 100 = 6.21¢ → 6¢
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::One).estimate_cost_in_usd_cents(), 6);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::One).calculate_costs().usd_cents_rounded_up, 7); // 1200/193 = 6.22 -> rounds UP
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_batch_two() {
|
||||
// 24 / 193 × 100 = 12.43¢ → 12¢
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Two).estimate_cost_in_usd_cents(), 12);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Two).calculate_costs().usd_cents_rounded_up, 13); // 2400/193 = 12.44 -> rounds UP
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usd_cents_batch_four() {
|
||||
// 48 / 193 × 100 = 24.87¢ → 25¢
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Four).estimate_cost_in_usd_cents(), 25);
|
||||
assert_eq!(make_request(KinoviMidjourneyBatchCount::Four).calculate_costs().usd_cents_rounded_up, 25); // 4800/193 = 24.87 -> rounds UP
|
||||
}
|
||||
|
||||
/// Batch 4 should be exactly 4× the credit cost of batch 1, and the USD
|
||||
@@ -431,13 +439,16 @@ mod tests {
|
||||
let one = make_request(KinoviMidjourneyBatchCount::One);
|
||||
let four = make_request(KinoviMidjourneyBatchCount::Four);
|
||||
|
||||
assert_eq!(four.estimate_credits(), one.estimate_credits() * 4);
|
||||
assert_eq!(four.calculate_costs().kinovi_credits, one.calculate_costs().kinovi_credits * 4);
|
||||
|
||||
let one_cents = one.estimate_cost_in_usd_cents();
|
||||
let four_cents = four.estimate_cost_in_usd_cents();
|
||||
let expected = one_cents * 4;
|
||||
let delta = (four_cents as i64 - expected as i64).abs();
|
||||
assert!(delta <= 1, "expected ~{}¢, got {}¢", expected, four_cents);
|
||||
// With round-up pricing, a batch rounds up ONCE while four singles
|
||||
// round up four times — so the batch price is at most 4× the single
|
||||
// price, and within 4¢ of it.
|
||||
let one_cents = one.calculate_costs().usd_cents_rounded_up;
|
||||
let four_cents = four.calculate_costs().usd_cents_rounded_up;
|
||||
let four_singles = one_cents * 4;
|
||||
assert!(four_cents <= four_singles && four_cents + 4 > four_singles,
|
||||
"expected within 4¢ under {}¢, got {}¢", four_singles, four_cents);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,6 +137,10 @@ pub struct OrderStatus {
|
||||
/// polling responses that didn't include the field — those came from the
|
||||
/// video-only era and can be treated as video by callers that need to.
|
||||
pub media_type: Option<OrderMediaType>,
|
||||
|
||||
/// The Kinovi credits charged for the order (the API's `totalCredits`).
|
||||
/// `None` for older polling responses that didn't include the field.
|
||||
pub total_credits: Option<u32>,
|
||||
}
|
||||
|
||||
// --- Implementation ---
|
||||
@@ -239,6 +243,7 @@ pub async fn poll_orders(args: PollOrdersArgs<'_>) -> Result<PollOrdersResponse,
|
||||
created_at: o.created_at,
|
||||
created_at_utc,
|
||||
media_type,
|
||||
total_credits: o.total_credits,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@@ -274,6 +279,48 @@ mod tests {
|
||||
Ok(Seedance2ProSession::from_cookies_string(cookies))
|
||||
}
|
||||
|
||||
/// Diagnostic: fetch the raw getOrders JSON (no typed parsing) and print
|
||||
/// the complete field set per order, so schema additions can be checked
|
||||
/// against reality. Requires real cookies.
|
||||
/// cargo test -p seedance2pro_client live_dump_raw_orders_json -- --ignored --nocapture
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn live_dump_raw_orders_json() -> AnyhowResult<()> {
|
||||
let session = test_session()?;
|
||||
let host = resolve_host(None);
|
||||
let base_url = host.api_base_url();
|
||||
let get_orders_url = format!("{}/api/trpc/userOrder.getOrders", base_url);
|
||||
let input_json = build_input_json(None);
|
||||
|
||||
let client = Client::builder()
|
||||
.emulation(Emulation::Firefox143)
|
||||
.build()?;
|
||||
|
||||
let response = client.get(&get_orders_url)
|
||||
.query(&[("batch", "1"), ("input", input_json.as_str())])
|
||||
.header("User-Agent", FIREFOX_USER_AGENT)
|
||||
.header("Accept", "*/*")
|
||||
.header("Referer", format!("{}/app/gallery", base_url))
|
||||
.header("content-type", "application/json")
|
||||
.header("x-trpc-source", "client")
|
||||
.header("Cookie", session.cookies.as_str())
|
||||
.send()
|
||||
.await?;
|
||||
|
||||
let body = response.text().await?;
|
||||
let parsed: serde_json::Value = serde_json::from_str(&body)?;
|
||||
let orders = &parsed[0]["result"]["data"]["json"]["orders"];
|
||||
|
||||
if let Some(first) = orders.get(0) {
|
||||
let keys: Vec<&str> = first.as_object().map(|o| o.keys().map(|k| k.as_str()).collect()).unwrap_or_default();
|
||||
println!("FIELDS ON AN ORDER: {:?}", keys);
|
||||
println!("FIRST ORDER RAW: {}", serde_json::to_string_pretty(first)?);
|
||||
} else {
|
||||
println!("no orders; raw body head: {}", &body[..body.len().min(500)]);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ── Offline parsing tests against captured responses ──
|
||||
//
|
||||
// These verify the deserializer + mapping handle the Midjourney image
|
||||
@@ -385,6 +432,7 @@ mod tests {
|
||||
created_at: o.created_at,
|
||||
created_at_utc,
|
||||
media_type,
|
||||
total_credits: o.total_credits,
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
|
||||
@@ -36,6 +36,11 @@ pub(super) struct RawOrder {
|
||||
/// orders. `None` for older response shapes that didn't include the field.
|
||||
#[serde(default)]
|
||||
pub media_type: Option<String>,
|
||||
|
||||
/// The Kinovi credits charged for the order. `None` for older response
|
||||
/// shapes that didn't include the field.
|
||||
#[serde(default)]
|
||||
pub total_credits: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
|
||||
+1
@@ -88,6 +88,7 @@ pub async fn insert_generic_inference_job_for_beeble_queue_with_apriori_job_toke
|
||||
creator_ip_address: args.creator_ip_address,
|
||||
creator_set_visibility: args.creator_set_visibility,
|
||||
maybe_platform_type: args.maybe_platform_type,
|
||||
maybe_cost_estimates: None,
|
||||
maybe_debug_log_event_token: args.maybe_debug_log_event_token,
|
||||
maybe_frontend_failure_category: args.maybe_frontend_failure_category,
|
||||
maybe_failure_reason: args.maybe_failure_reason,
|
||||
|
||||
+6
@@ -36,6 +36,7 @@ use tokens::tokens::users::UserToken;
|
||||
use tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken;
|
||||
|
||||
use crate::errors::database_query_error::DatabaseQueryError;
|
||||
use crate::queries::generic_inference::common::job_cost_estimates::JobCostEstimates;
|
||||
use crate::payloads::generic_inference_args::generic_inference_args::GenericInferenceArgs;
|
||||
use crate::queries::generic_inference::common::insert_full_generic_inference_job_record::{
|
||||
insert_full_generic_inference_job_record,
|
||||
@@ -75,6 +76,9 @@ pub(crate) struct InsertGenericInferenceJobForProviderArgs<'e, 'c, E>
|
||||
/// The platform the enqueuing request came from, inferred from its User-Agent.
|
||||
pub maybe_platform_type: Option<PlatformType>,
|
||||
|
||||
/// Estimated system (user-facing) and provider-side costs.
|
||||
pub maybe_cost_estimates: Option<JobCostEstimates>,
|
||||
|
||||
pub maybe_debug_log_event_token: Option<&'e DebugLogEventToken>,
|
||||
|
||||
pub maybe_frontend_failure_category: Option<FrontendFailureCategory>,
|
||||
@@ -120,6 +124,8 @@ pub(crate) async fn insert_generic_inference_job_for_provider<'e, 'c: 'e, E>(
|
||||
|
||||
maybe_platform_type: args.maybe_platform_type,
|
||||
|
||||
maybe_cost_estimates: args.maybe_cost_estimates,
|
||||
|
||||
maybe_debug_log_event_token: args.maybe_debug_log_event_token,
|
||||
|
||||
maybe_frontend_failure_category: args.maybe_frontend_failure_category,
|
||||
|
||||
+1
@@ -71,6 +71,7 @@ pub async fn insert_generic_inference_job_for_fal_queue<'e, 'c : 'e, E>(args: In
|
||||
creator_ip_address: args.creator_ip_address,
|
||||
creator_set_visibility: args.creator_set_visibility,
|
||||
maybe_platform_type: args.maybe_platform_type,
|
||||
maybe_cost_estimates: None,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
|
||||
+1
@@ -74,6 +74,7 @@ pub async fn insert_generic_inference_job_for_fal_queue_mock_failure<'e, 'c: 'e,
|
||||
creator_ip_address: args.creator_ip_address,
|
||||
creator_set_visibility: args.creator_set_visibility,
|
||||
maybe_platform_type: args.maybe_platform_type,
|
||||
maybe_cost_estimates: None,
|
||||
starting_job_status_override: Some(JobStatusPlus::CompleteFailure),
|
||||
maybe_frontend_failure_category: args.maybe_frontend_failure_category,
|
||||
maybe_failure_reason: args.maybe_failure_reason,
|
||||
|
||||
+5
@@ -18,6 +18,7 @@ use tokens::tokens::prompts::PromptToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::errors::database_query_error::DatabaseQueryError;
|
||||
use crate::queries::generic_inference::common::job_cost_estimates::JobCostEstimates;
|
||||
use crate::payloads::generic_inference_args::generic_inference_args::GenericInferenceArgs;
|
||||
use crate::queries::generic_inference::api_providers::common::insert_generic_inference_job_for_provider::{
|
||||
insert_generic_inference_job_for_provider,
|
||||
@@ -53,6 +54,9 @@ pub struct InsertGenericInferenceForFalWithAprioriJobTokenArgs<'e, 'c, E>
|
||||
/// The platform the enqueuing request came from, inferred from its User-Agent.
|
||||
pub maybe_platform_type: Option<PlatformType>,
|
||||
|
||||
/// Estimated system (user-facing) and provider-side costs.
|
||||
pub maybe_cost_estimates: Option<JobCostEstimates>,
|
||||
|
||||
/// Override the initial job status. Defaults to `Pending` when `None`.
|
||||
/// Set to `Some(JobStatusPlus::CompleteFailure)` for mock/test failure jobs.
|
||||
pub starting_job_status_override: Option<JobStatusPlus>,
|
||||
@@ -110,6 +114,7 @@ pub async fn insert_generic_inference_job_for_fal_queue_with_apriori_job_token<'
|
||||
creator_ip_address: args.creator_ip_address,
|
||||
creator_set_visibility: args.creator_set_visibility,
|
||||
maybe_platform_type: args.maybe_platform_type,
|
||||
maybe_cost_estimates: args.maybe_cost_estimates,
|
||||
maybe_debug_log_event_token: args.maybe_debug_log_event_token,
|
||||
maybe_frontend_failure_category: args.maybe_frontend_failure_category,
|
||||
maybe_failure_reason: args.maybe_failure_reason,
|
||||
|
||||
+5
@@ -17,6 +17,7 @@ use tokens::tokens::prompts::PromptToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::errors::database_query_error::DatabaseQueryError;
|
||||
use crate::queries::generic_inference::common::job_cost_estimates::JobCostEstimates;
|
||||
use crate::queries::generic_inference::api_providers::common::insert_generic_inference_job_for_provider::{
|
||||
insert_generic_inference_job_for_provider,
|
||||
InsertGenericInferenceJobForProviderArgs,
|
||||
@@ -35,6 +36,9 @@ where
|
||||
pub maybe_avt_token: Option<&'e AnonymousVisitorTrackingToken>,
|
||||
pub creator_ip_address: &'e str,
|
||||
pub maybe_platform_type: Option<PlatformType>,
|
||||
|
||||
/// Estimated system (user-facing) and provider-side costs.
|
||||
pub maybe_cost_estimates: Option<JobCostEstimates>,
|
||||
pub maybe_debug_log_event_token: Option<&'e DebugLogEventToken>,
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
@@ -63,6 +67,7 @@ where
|
||||
creator_ip_address: args.creator_ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: args.maybe_platform_type,
|
||||
maybe_cost_estimates: args.maybe_cost_estimates,
|
||||
maybe_debug_log_event_token: args.maybe_debug_log_event_token,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
|
||||
+5
@@ -17,6 +17,7 @@ use tokens::tokens::prompts::PromptToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::errors::database_query_error::DatabaseQueryError;
|
||||
use crate::queries::generic_inference::common::job_cost_estimates::JobCostEstimates;
|
||||
use crate::queries::generic_inference::api_providers::common::insert_generic_inference_job_for_provider::{
|
||||
insert_generic_inference_job_for_provider,
|
||||
InsertGenericInferenceJobForProviderArgs,
|
||||
@@ -35,6 +36,9 @@ where
|
||||
pub maybe_avt_token: Option<&'e AnonymousVisitorTrackingToken>,
|
||||
pub creator_ip_address: &'e str,
|
||||
pub maybe_platform_type: Option<PlatformType>,
|
||||
|
||||
/// Estimated system (user-facing) and provider-side costs.
|
||||
pub maybe_cost_estimates: Option<JobCostEstimates>,
|
||||
pub maybe_debug_log_event_token: Option<&'e DebugLogEventToken>,
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
@@ -63,6 +67,7 @@ where
|
||||
creator_ip_address: args.creator_ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: args.maybe_platform_type,
|
||||
maybe_cost_estimates: args.maybe_cost_estimates,
|
||||
maybe_debug_log_event_token: args.maybe_debug_log_event_token,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
|
||||
+1
@@ -64,6 +64,7 @@ pub async fn insert_generic_inference_job_for_seedance2pro_character_with_aprior
|
||||
creator_ip_address: args.creator_ip_address,
|
||||
creator_set_visibility: args.creator_set_visibility,
|
||||
maybe_platform_type: None,
|
||||
maybe_cost_estimates: None,
|
||||
maybe_debug_log_event_token: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
|
||||
+5
@@ -18,6 +18,7 @@ use tokens::tokens::users::UserToken;
|
||||
use tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken;
|
||||
|
||||
use crate::errors::database_query_error::DatabaseQueryError;
|
||||
use crate::queries::generic_inference::common::job_cost_estimates::JobCostEstimates;
|
||||
use crate::payloads::generic_inference_args::generic_inference_args::GenericInferenceArgs;
|
||||
use crate::queries::generic_inference::api_providers::common::insert_generic_inference_job_for_provider::{
|
||||
insert_generic_inference_job_for_provider,
|
||||
@@ -54,6 +55,9 @@ pub struct InsertGenericInferenceForSeedance2ProWithAprioriJobTokenArgs<'e, 'c,
|
||||
/// The platform the enqueuing request came from, inferred from its User-Agent.
|
||||
pub maybe_platform_type: Option<PlatformType>,
|
||||
|
||||
/// Estimated system (user-facing) and provider-side costs.
|
||||
pub maybe_cost_estimates: Option<JobCostEstimates>,
|
||||
|
||||
pub maybe_debug_log_event_token: Option<&'e DebugLogEventToken>,
|
||||
|
||||
pub mysql_executor: E,
|
||||
@@ -131,6 +135,7 @@ pub async fn insert_generic_inference_job_for_seedance2pro_queue_with_apriori_jo
|
||||
creator_ip_address: args.creator_ip_address,
|
||||
creator_set_visibility: args.creator_set_visibility,
|
||||
maybe_platform_type: args.maybe_platform_type,
|
||||
maybe_cost_estimates: args.maybe_cost_estimates,
|
||||
maybe_debug_log_event_token: args.maybe_debug_log_event_token,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
|
||||
+1
@@ -76,6 +76,7 @@ pub async fn insert_generic_inference_job_for_worldlabs_queue_with_apriori_job_t
|
||||
creator_ip_address: args.creator_ip_address,
|
||||
creator_set_visibility: args.creator_set_visibility,
|
||||
maybe_platform_type: args.maybe_platform_type,
|
||||
maybe_cost_estimates: None,
|
||||
maybe_debug_log_event_token: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
|
||||
+15
@@ -38,6 +38,7 @@ use tokens::tokens::users::UserToken;
|
||||
use tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken;
|
||||
|
||||
use crate::errors::database_query_error::DatabaseQueryError;
|
||||
use crate::queries::generic_inference::common::job_cost_estimates::JobCostEstimates;
|
||||
use crate::payloads::generic_inference_args::generic_inference_args::GenericInferenceArgs;
|
||||
|
||||
pub(crate) struct InsertFullGenericInferenceJobRecordArgs<'e, 'c, E>
|
||||
@@ -80,6 +81,10 @@ pub(crate) struct InsertFullGenericInferenceJobRecordArgs<'e, 'c, E>
|
||||
/// The platform the enqueuing request came from, inferred from its User-Agent.
|
||||
pub maybe_platform_type: Option<PlatformType>,
|
||||
|
||||
/// Estimated system (user-facing) and provider-side costs. `None` writes
|
||||
/// DB NULL for all four columns.
|
||||
pub maybe_cost_estimates: Option<JobCostEstimates>,
|
||||
|
||||
pub priority_level: u8,
|
||||
pub requires_keepalive: bool,
|
||||
pub max_duration_seconds: i32,
|
||||
@@ -163,6 +168,11 @@ SET
|
||||
|
||||
platform_type = ?,
|
||||
|
||||
maybe_external_third_party_cost_credits = ?,
|
||||
maybe_external_third_party_cost_usd_cents = ?,
|
||||
maybe_system_cost_credits = ?,
|
||||
maybe_system_cost_usd_cents = ?,
|
||||
|
||||
priority_level = ?,
|
||||
is_keepalive_required = ?,
|
||||
max_duration_seconds = ?,
|
||||
@@ -211,6 +221,11 @@ SET
|
||||
|
||||
args.maybe_platform_type.map(|p| p.to_str()),
|
||||
|
||||
args.maybe_cost_estimates.and_then(|c| c.maybe_external_third_party_cost_credits),
|
||||
args.maybe_cost_estimates.and_then(|c| c.maybe_external_third_party_cost_usd_cents),
|
||||
args.maybe_cost_estimates.and_then(|c| c.maybe_system_cost_credits),
|
||||
args.maybe_cost_estimates.and_then(|c| c.maybe_system_cost_usd_cents),
|
||||
|
||||
args.priority_level,
|
||||
args.requires_keepalive,
|
||||
args.max_duration_seconds,
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
/// Estimated costs for an inference job, captured at enqueue time.
|
||||
///
|
||||
/// "System" costs are what we charge the user (Artcraft pricing).
|
||||
/// "External third party" costs are what the fulfilling provider (Kinovi,
|
||||
/// Fal, GmiCloud, Grok, etc.) charges us, in the provider's own credit
|
||||
/// denomination and/or USD cents.
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct JobCostEstimates {
|
||||
/// Provider-side cost in the provider's own credits (if it bills in credits).
|
||||
pub maybe_external_third_party_cost_credits: Option<u32>,
|
||||
|
||||
/// Provider-side cost in USD cents.
|
||||
pub maybe_external_third_party_cost_usd_cents: Option<u32>,
|
||||
|
||||
/// User-facing cost in our own credits.
|
||||
pub maybe_system_cost_credits: Option<u32>,
|
||||
|
||||
/// User-facing cost in USD cents.
|
||||
pub maybe_system_cost_usd_cents: Option<u32>,
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
pub(crate) mod insert_full_generic_inference_job_record;
|
||||
pub mod job_cost_estimates;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
pub mod api_providers;
|
||||
pub(crate) mod common;
|
||||
pub mod common;
|
||||
pub mod job;
|
||||
pub mod moderation;
|
||||
pub mod web;
|
||||
|
||||
+2
@@ -123,6 +123,8 @@ pub async fn insert_generic_inference_job(args: InsertGenericInferenceArgs<'_>)
|
||||
|
||||
maybe_platform_type: None,
|
||||
|
||||
maybe_cost_estimates: None,
|
||||
|
||||
maybe_debug_log_event_token: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
maybe_failure_reason: None,
|
||||
|
||||
+1
@@ -241,6 +241,7 @@ pub async fn flux_2_lora_edit_image_angle_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -242,6 +242,7 @@ pub async fn qwen_edit_2511_edit_image_angle_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -266,6 +266,7 @@ pub async fn gemini_25_flash_edit_image_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -298,6 +298,7 @@ pub async fn gpt_image_1_edit_image_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -271,6 +271,7 @@ pub async fn qwen_edit_image_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -320,6 +320,7 @@ pub async fn bytedance_seedream_5_lite_multi_function_image_gen_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -341,6 +341,7 @@ pub async fn bytedance_seedream_v4_multi_function_image_gen_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -338,6 +338,7 @@ pub async fn bytedance_seedream_v4p5_multi_function_image_gen_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -389,6 +389,7 @@ pub async fn gpt_image_1p5_multi_function_image_gen_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -350,6 +350,7 @@ pub async fn nano_banana_2_multi_function_image_gen_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -338,6 +338,7 @@ pub async fn nano_banana_multi_function_image_gen_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -359,6 +359,7 @@ pub async fn nano_banana_pro_multi_function_image_gen_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -218,6 +218,7 @@ pub async fn generate_flux_pro_11_text_to_image_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -226,6 +226,7 @@ pub async fn generate_flux_pro_11_ultra_text_to_image_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -232,6 +232,7 @@ pub async fn generate_gpt_image_1_text_to_image_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -267,6 +267,7 @@ pub async fn generate_kling_1_6_pro_video_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -254,6 +254,7 @@ pub async fn generate_kling_2_1_master_video_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -270,6 +270,7 @@ pub async fn generate_kling_2_1_pro_video_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -288,6 +288,7 @@ pub async fn generate_seedance_1_0_lite_image_to_video_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -262,6 +262,7 @@ pub async fn generate_seedance_1_0_pro_image_to_video_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -259,6 +259,7 @@ pub async fn generate_veo_2_image_to_video_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -264,6 +264,7 @@ pub async fn generate_veo_3_fast_image_to_video_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -268,6 +268,7 @@ pub async fn generate_veo_3_image_to_video_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -347,6 +347,7 @@ pub async fn kling_2p5_turbo_pro_multi_function_video_gen_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -344,6 +344,7 @@ pub async fn kling_2p6_pro_multi_function_video_gen_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -349,6 +349,7 @@ pub async fn kling_3p0_pro_multi_function_video_gen_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -349,6 +349,7 @@ pub async fn kling_3p0_standard_multi_function_video_gen_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -336,6 +336,7 @@ pub async fn seedance_1p5_pro_multi_function_video_gen_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
starting_job_status_override: None,
|
||||
maybe_frontend_failure_category: None,
|
||||
|
||||
+1
@@ -436,6 +436,7 @@ pub async fn seedance_2p0_multi_function_video_gen_handler(
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
maybe_platform_type: get_request_platform_type(&http_request),
|
||||
maybe_cost_estimates: None,
|
||||
maybe_debug_log_event_token: None,
|
||||
mysql_executor: &mut *transaction,
|
||||
phantom: Default::default(),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user