mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-22 09:39:31 -04:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2929eae094 | ||
|
|
b47f9a522a |
Generated
+1
@@ -11667,6 +11667,7 @@ dependencies = [
|
||||
"utoipa",
|
||||
"utoipa-swagger-ui",
|
||||
"uuid_utils",
|
||||
"video_info",
|
||||
"videos",
|
||||
"workspace-build-acceleration",
|
||||
"worldlabs_api_client",
|
||||
|
||||
@@ -16,4 +16,5 @@ pub mod subscriptions;
|
||||
pub mod user_referral_codes;
|
||||
pub mod users;
|
||||
pub mod utils;
|
||||
pub mod video_info;
|
||||
pub mod web_referrals;
|
||||
@@ -0,0 +1 @@
|
||||
pub mod read_only;
|
||||
@@ -0,0 +1,167 @@
|
||||
//! Response types for `POST /v1/video_info/read_only`.
|
||||
//!
|
||||
//! The endpoint accepts a `multipart/form-data` video upload, inspects its
|
||||
//! embedded provenance (C2PA manifests, AIGC labels, in-stream watermarks), and
|
||||
//! returns what it found. These are the public, wire-stable API types — they
|
||||
//! deliberately mirror, but do not re-export, the internal `video_info` crate
|
||||
//! types so the parsing library can evolve independently of the HTTP contract.
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// Response for `POST /v1/video_info/read_only`.
|
||||
///
|
||||
/// [`kind`](Self::kind) names the detected provenance family; the matching
|
||||
/// `maybe_*` field carries its details (all others are `null`). `unrecognized`
|
||||
/// means no supported provenance was found — [`maybe_encoder`](Self::maybe_encoder)
|
||||
/// still reports the container encoder tag when present (an `Lavf…` value means
|
||||
/// the file was re-encoded through ffmpeg, which strips provenance).
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
|
||||
pub struct VideoInfoReadOnlyResponse {
|
||||
pub success: bool,
|
||||
|
||||
/// Which provenance family was detected (if any).
|
||||
pub kind: VideoProvenanceKind,
|
||||
|
||||
/// The container `©too` encoder tag, e.g. `"Lavf60.16.100"` or `"Google"`.
|
||||
pub maybe_encoder: Option<String>,
|
||||
|
||||
/// Present when [`kind`](Self::kind) is [`VideoProvenanceKind::Seedance`].
|
||||
pub maybe_seedance: Option<SeedanceVideoInfo>,
|
||||
|
||||
/// Present when [`kind`](Self::kind) is [`VideoProvenanceKind::Veo`].
|
||||
pub maybe_veo: Option<VeoVideoInfo>,
|
||||
|
||||
/// Present when [`kind`](Self::kind) is [`VideoProvenanceKind::Sora`].
|
||||
pub maybe_sora: Option<SoraVideoInfo>,
|
||||
|
||||
/// Present when [`kind`](Self::kind) is [`VideoProvenanceKind::Dreamina`].
|
||||
pub maybe_dreamina: Option<DreaminaVideoInfo>,
|
||||
|
||||
/// Present when [`kind`](Self::kind) is [`VideoProvenanceKind::Kling`].
|
||||
pub maybe_kling: Option<KlingVideoInfo>,
|
||||
}
|
||||
|
||||
/// The recognized AI-video provenance family.
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum VideoProvenanceKind {
|
||||
/// ByteDance Seedance API (Volcengine / BytePlus C2PA).
|
||||
Seedance,
|
||||
/// Google Veo (Google Generative AI video C2PA).
|
||||
Veo,
|
||||
/// OpenAI Sora (OpenAI Generative AI video C2PA).
|
||||
Sora,
|
||||
/// Dreamina (ByteDance/CapCut consumer app `ilst` metadata).
|
||||
Dreamina,
|
||||
/// Kling (Kuaishou AIGC-label / in-stream watermark).
|
||||
Kling,
|
||||
/// No supported provenance was found (often a re-encoded / stripped file).
|
||||
Unrecognized,
|
||||
}
|
||||
|
||||
/// Seedance (ByteDance) provenance from an embedded C2PA manifest.
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
|
||||
pub struct SeedanceVideoInfo {
|
||||
/// `"volcengine"` or `"byteplus"`.
|
||||
pub platform: String,
|
||||
/// C2PA `softwareAgent`, e.g. `"Volcengine_Ark_CN"`.
|
||||
pub software_agent: String,
|
||||
pub maybe_software_agent_version: Option<String>,
|
||||
/// Full model identifier, e.g. `"doubao-seedance-2-0-fast"`.
|
||||
pub model_name: String,
|
||||
pub maybe_model_brand: Option<String>,
|
||||
/// Parsed version, e.g. `"2.0"` / `"1.0"`.
|
||||
pub maybe_model_version: Option<String>,
|
||||
pub is_fast: bool,
|
||||
pub is_lite: bool,
|
||||
/// Raw generation timestamp string (RFC 3339).
|
||||
pub generated_at: String,
|
||||
/// Normalized RFC 3339 timestamp, when parseable.
|
||||
pub maybe_generated_at_utc: Option<String>,
|
||||
pub maybe_log_id: Option<String>,
|
||||
pub maybe_log_id_decoded_hex: Option<String>,
|
||||
pub maybe_digital_source_type: Option<String>,
|
||||
pub maybe_claim_generator: Option<String>,
|
||||
pub maybe_claim_generator_version: Option<String>,
|
||||
pub maybe_manifest_id: Option<String>,
|
||||
pub maybe_instance_id: Option<String>,
|
||||
pub maybe_signer_email: Option<String>,
|
||||
pub maybe_signer_org_id: Option<String>,
|
||||
pub maybe_signer_country: Option<String>,
|
||||
pub maybe_cert_serial: Option<String>,
|
||||
}
|
||||
|
||||
/// Google Veo provenance.
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
|
||||
pub struct VeoVideoInfo {
|
||||
/// Always `"Google"`.
|
||||
pub producer: String,
|
||||
/// Whether a full Google C2PA manifest is present (`false` = recognized only
|
||||
/// by the `encoder=Google` tag, C2PA likely stripped by a re-encode).
|
||||
pub has_c2pa_manifest: bool,
|
||||
pub maybe_encoder: Option<String>,
|
||||
pub maybe_created_description: Option<String>,
|
||||
pub has_synthid_watermark: bool,
|
||||
pub maybe_synthid_description: Option<String>,
|
||||
pub maybe_digital_source_type: Option<String>,
|
||||
pub maybe_claim_generator: Option<String>,
|
||||
pub maybe_claim_generator_version: Option<String>,
|
||||
pub maybe_manifest_id: Option<String>,
|
||||
pub maybe_instance_id: Option<String>,
|
||||
pub maybe_cert_serial: Option<String>,
|
||||
/// Issuing intermediate-CA common name, e.g. `"Google C2PA Media Services 1P ICA G3"`.
|
||||
pub maybe_signer_ca: Option<String>,
|
||||
/// Whether the manifest carries an RFC 3161 trusted timestamp.
|
||||
pub is_timestamped: bool,
|
||||
pub maybe_timestamp_authority: Option<String>,
|
||||
}
|
||||
|
||||
/// OpenAI Sora provenance.
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
|
||||
pub struct SoraVideoInfo {
|
||||
/// Always `"OpenAI"`.
|
||||
pub producer: String,
|
||||
pub maybe_model_name: Option<String>,
|
||||
pub maybe_created_description: Option<String>,
|
||||
pub maybe_digital_source_type: Option<String>,
|
||||
pub maybe_claim_generator: Option<String>,
|
||||
pub maybe_manifest_id: Option<String>,
|
||||
pub maybe_instance_id: Option<String>,
|
||||
pub maybe_cert_serial: Option<String>,
|
||||
}
|
||||
|
||||
/// Dreamina (ByteDance/CapCut app) provenance from `ilst` metadata.
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
|
||||
pub struct DreaminaVideoInfo {
|
||||
/// The product string, e.g. `"dreamina"`.
|
||||
pub product: String,
|
||||
pub maybe_export_type: Option<String>,
|
||||
pub maybe_os: Option<String>,
|
||||
pub maybe_source_info: Option<String>,
|
||||
pub maybe_aigc_label_type: Option<i64>,
|
||||
pub maybe_video_id: Option<String>,
|
||||
pub has_c2pa: bool,
|
||||
pub maybe_signer_org_id: Option<String>,
|
||||
pub maybe_signer_country: Option<String>,
|
||||
pub maybe_cert_serial: Option<String>,
|
||||
}
|
||||
|
||||
/// Kling (Kuaishou) provenance from the AIGC label and/or in-stream watermark.
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug, Clone)]
|
||||
pub struct KlingVideoInfo {
|
||||
/// Model version when a clean export embeds it (e.g. `"2.5-pro"`); usually `null`.
|
||||
pub maybe_model_version: Option<String>,
|
||||
/// Whether labeled AI-generated (`Label == "1"`, per China GB 45438).
|
||||
pub is_ai_generated: bool,
|
||||
pub maybe_label: Option<String>,
|
||||
pub maybe_content_producer: Option<String>,
|
||||
pub maybe_produce_id: Option<String>,
|
||||
pub maybe_content_propagator: Option<String>,
|
||||
pub maybe_propagate_id: Option<String>,
|
||||
pub maybe_reserved_code_1: Option<String>,
|
||||
pub maybe_reserved_code_2: Option<String>,
|
||||
/// Whether the in-stream `kling-ai` SEI watermark is present.
|
||||
pub has_stream_watermark: bool,
|
||||
pub maybe_watermark_uuid: Option<String>,
|
||||
}
|
||||
@@ -38,6 +38,7 @@ actix_helpers.workspace = true
|
||||
artcraft_api_defs.workspace = true
|
||||
artcraft_api_keys.workspace = true
|
||||
artcraft_router.workspace = true
|
||||
video_info.workspace = true
|
||||
bootstrap.workspace = true
|
||||
bucket_paths.workspace = true
|
||||
cloud_storage.workspace = true
|
||||
|
||||
@@ -116,6 +116,19 @@ pub fn configure_redis_rate_limiters() -> AnyhowResult<RedisRateLimiters> {
|
||||
RedisRateLimiter::new(limiter, "file_upload_logged_in", limiter_enabled)
|
||||
};
|
||||
|
||||
let video_info_read_only_rate_limiter = {
|
||||
let limiter_enabled = easyenv::get_env_bool_or_default("LIMITER_VIDEO_INFO_READ_ONLY_ENABLED", true);
|
||||
let limiter_max_requests = easyenv::get_env_num("LIMITER_VIDEO_INFO_READ_ONLY_MAX_REQUESTS", 1)?;
|
||||
let limiter_window_seconds = easyenv::get_env_num("LIMITER_VIDEO_INFO_READ_ONLY_WINDOW_SECONDS", 5)?;
|
||||
|
||||
let limiter = Limiter::build(&redis_connection_string)
|
||||
.limit(limiter_max_requests)
|
||||
.period(Duration::from_secs(limiter_window_seconds))
|
||||
.finish()?;
|
||||
|
||||
RedisRateLimiter::new(limiter, "video_info_read_only", limiter_enabled)
|
||||
};
|
||||
|
||||
Ok(RedisRateLimiters {
|
||||
logged_out: logged_out_redis_rate_limiter,
|
||||
logged_in: logged_in_redis_rate_limiter,
|
||||
@@ -125,5 +138,6 @@ pub fn configure_redis_rate_limiters() -> AnyhowResult<RedisRateLimiters> {
|
||||
model_upload: model_upload_rate_limiter,
|
||||
file_upload_logged_out: file_upload_logged_out_redis_rate_limiter,
|
||||
file_upload_logged_in: file_upload_logged_in_redis_rate_limiter,
|
||||
video_info_read_only: video_info_read_only_rate_limiter,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -364,6 +364,8 @@ use artcraft_api_defs::user_referral_codes::create_referral_code::*;
|
||||
use artcraft_api_defs::user_referral_codes::delete_referral_code::*;
|
||||
use artcraft_api_defs::user_referral_codes::list_referral_codes::*;
|
||||
use artcraft_api_defs::web_referrals::log_web_referral::*;
|
||||
use artcraft_api_defs::video_info::read_only::*;
|
||||
use crate::http_server::endpoints::video_info::video_info_read_info_handler::*;
|
||||
use crate::http_server::endpoints::web_referrals::log_web_referral_handler::*;
|
||||
use crate::http_server::endpoints::image_studio::update_gpt_image_job_status_handler::*;
|
||||
use crate::http_server::endpoints::voice_conversion::enqueue_seed_vc_inference_handler::*;
|
||||
@@ -617,6 +619,7 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
|
||||
// Subscriptions
|
||||
crate::http_server::endpoints::subscriptions::get_session_subscription_handler::get_session_subscription_handler,
|
||||
// Web Referrals
|
||||
crate::http_server::endpoints::video_info::video_info_read_info_handler::video_info_read_info_handler,
|
||||
crate::http_server::endpoints::web_referrals::log_web_referral_handler::log_web_referral_handler,
|
||||
// User Referral Codes
|
||||
crate::http_server::endpoints::user_referral_codes::create_referral_code_handler::create_referral_code_handler,
|
||||
@@ -1249,6 +1252,16 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
|
||||
Veo3p1FastMultiFunctionVideoGenRequest,
|
||||
Veo3p1FastMultiFunctionVideoGenResponse,
|
||||
|
||||
// Video info (provenance detection)
|
||||
VideoInfoReadOnlyForm,
|
||||
VideoInfoReadOnlyResponse,
|
||||
VideoProvenanceKind,
|
||||
SeedanceVideoInfo,
|
||||
VeoVideoInfo,
|
||||
SoraVideoInfo,
|
||||
DreaminaVideoInfo,
|
||||
KlingVideoInfo,
|
||||
|
||||
// Object Multi-Function types
|
||||
Hunyuan3dV3MultiFunctionObjectGenRequest,
|
||||
Hunyuan3dV3MultiFunctionObjectGenResponse,
|
||||
|
||||
@@ -32,6 +32,7 @@ pub mod user_ratings;
|
||||
pub mod user_referral_codes;
|
||||
pub mod user_wallets;
|
||||
pub mod users;
|
||||
pub mod video_info;
|
||||
pub mod voice_clone_requests;
|
||||
pub mod voice_conversion;
|
||||
pub mod voice_designer;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pub mod video_info_read_info_handler;
|
||||
+224
@@ -0,0 +1,224 @@
|
||||
//! `POST /v1/video_info/read_only` — inspect an uploaded video's embedded
|
||||
//! AI-generation provenance and return what was detected.
|
||||
//!
|
||||
//! Stateless and read-only: the bytes are parsed in memory by the `video_info`
|
||||
//! crate and nothing is persisted. The internal `video_info` types are mapped
|
||||
//! into the public [`artcraft_api_defs`] wire types so the parsing library can
|
||||
//! evolve independently of the HTTP contract.
|
||||
|
||||
use std::io::Read;
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_multipart::form::tempfile::TempFile;
|
||||
use actix_multipart::form::MultipartForm;
|
||||
use actix_web::web::Json;
|
||||
use actix_web::{web, HttpRequest};
|
||||
use log::error;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use artcraft_api_defs::video_info::read_only::{
|
||||
DreaminaVideoInfo, KlingVideoInfo, SeedanceVideoInfo, SoraVideoInfo, VeoVideoInfo,
|
||||
VideoInfoReadOnlyResponse, VideoProvenanceKind,
|
||||
};
|
||||
use video_info::dreamina_info::DreaminaInfo;
|
||||
use video_info::kling_info::KlingInfo;
|
||||
use video_info::seedance_info::SeedanceInfo;
|
||||
use video_info::sora_info::SoraInfo;
|
||||
use video_info::veo_info::VeoInfo;
|
||||
use video_info::VideoInfo;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::redis_rate_limiter::RateLimiterError;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
/// Form-multipart request: a single video file under the `file` field.
|
||||
#[derive(MultipartForm, ToSchema)]
|
||||
#[multipart(duplicate_field = "deny")]
|
||||
pub struct VideoInfoReadOnlyForm {
|
||||
/// The video file to inspect.
|
||||
#[multipart(limit = "256 MiB")]
|
||||
#[schema(value_type = Vec<u8>, format = Binary)]
|
||||
file: TempFile,
|
||||
}
|
||||
|
||||
/// Detect the AI-generation provenance of an uploaded video.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
tag = "Video Info",
|
||||
path = "/v1/video_info/read_only",
|
||||
responses(
|
||||
(status = 200, description = "Detected provenance (or `unrecognized`)", body = VideoInfoReadOnlyResponse),
|
||||
(status = 400, description = "Bad input", body = CommonWebError),
|
||||
(status = 500, description = "Server error", body = CommonWebError),
|
||||
),
|
||||
params(
|
||||
("request" = VideoInfoReadOnlyForm, description = "Multipart form with a single `file` video upload."),
|
||||
)
|
||||
)]
|
||||
pub async fn video_info_read_info_handler(
|
||||
http_request: HttpRequest,
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
MultipartForm(mut form): MultipartForm<VideoInfoReadOnlyForm>,
|
||||
) -> Result<Json<VideoInfoReadOnlyResponse>, CommonWebError> {
|
||||
// IP-based rate limit (1 req / 5s by default, configurable). Fails open: the
|
||||
// limiter already treats Redis/parse errors as allowed, and we allow on any
|
||||
// non-"limit exceeded" outcome here too.
|
||||
match server_state.redis_rate_limiters.video_info_read_only.rate_limit_request(&http_request).await {
|
||||
Ok(()) => {}
|
||||
Err(RateLimiterError::RateLimitExceededError) => return Err(CommonWebError::TooManyRequests),
|
||||
Err(RateLimiterError::ClientError) => {} // fail open
|
||||
}
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
form.file.file.read_to_end(&mut bytes).map_err(|err| {
|
||||
error!("video_info: problem reading uploaded file: {:?}", err);
|
||||
CommonWebError::server_error_with_message("could not read uploaded file")
|
||||
})?;
|
||||
|
||||
if bytes.is_empty() {
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage("empty file".to_string()));
|
||||
}
|
||||
|
||||
let maybe_encoder = video_info::encoder_tag(&bytes);
|
||||
|
||||
// Any parse error (unrecognized / malformed manifest) collapses to
|
||||
// `Unrecognized` — we never surface internal error types to clients.
|
||||
let response = match VideoInfo::from_bytes(&bytes) {
|
||||
Ok(info) => to_response(info, maybe_encoder),
|
||||
Err(_) => unrecognized_response(maybe_encoder),
|
||||
};
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
// ── Mapping: internal `video_info` types → public API types ──
|
||||
|
||||
fn to_response(info: VideoInfo, maybe_encoder: Option<String>) -> VideoInfoReadOnlyResponse {
|
||||
let mut response = unrecognized_response(maybe_encoder);
|
||||
match info {
|
||||
VideoInfo::Seedance(seedance) => {
|
||||
response.kind = VideoProvenanceKind::Seedance;
|
||||
response.maybe_seedance = Some(to_seedance(seedance));
|
||||
}
|
||||
VideoInfo::Veo(veo) => {
|
||||
response.kind = VideoProvenanceKind::Veo;
|
||||
response.maybe_veo = Some(to_veo(veo));
|
||||
}
|
||||
VideoInfo::Sora(sora) => {
|
||||
response.kind = VideoProvenanceKind::Sora;
|
||||
response.maybe_sora = Some(to_sora(sora));
|
||||
}
|
||||
VideoInfo::Dreamina(dreamina) => {
|
||||
response.kind = VideoProvenanceKind::Dreamina;
|
||||
response.maybe_dreamina = Some(to_dreamina(dreamina));
|
||||
}
|
||||
VideoInfo::Kling(kling) => {
|
||||
response.kind = VideoProvenanceKind::Kling;
|
||||
response.maybe_kling = Some(to_kling(kling));
|
||||
}
|
||||
}
|
||||
response
|
||||
}
|
||||
|
||||
fn unrecognized_response(maybe_encoder: Option<String>) -> VideoInfoReadOnlyResponse {
|
||||
VideoInfoReadOnlyResponse {
|
||||
success: true,
|
||||
kind: VideoProvenanceKind::Unrecognized,
|
||||
maybe_encoder,
|
||||
maybe_seedance: None,
|
||||
maybe_veo: None,
|
||||
maybe_sora: None,
|
||||
maybe_dreamina: None,
|
||||
maybe_kling: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_seedance(info: SeedanceInfo) -> SeedanceVideoInfo {
|
||||
SeedanceVideoInfo {
|
||||
platform: info.platform.as_str().to_string(),
|
||||
software_agent: info.software_agent,
|
||||
maybe_software_agent_version: info.software_agent_version,
|
||||
model_name: info.model_name,
|
||||
maybe_model_brand: info.model_brand,
|
||||
maybe_model_version: info.model_version,
|
||||
is_fast: info.is_fast,
|
||||
is_lite: info.is_lite,
|
||||
generated_at: info.generated_at,
|
||||
maybe_generated_at_utc: info.generated_at_utc.map(|dt| dt.to_rfc3339()),
|
||||
maybe_log_id: info.log_id,
|
||||
maybe_log_id_decoded_hex: info.log_id_decoded_hex,
|
||||
maybe_digital_source_type: info.digital_source_type,
|
||||
maybe_claim_generator: info.claim_generator,
|
||||
maybe_claim_generator_version: info.claim_generator_version,
|
||||
maybe_manifest_id: info.manifest_id,
|
||||
maybe_instance_id: info.instance_id,
|
||||
maybe_signer_email: info.signer_email,
|
||||
maybe_signer_org_id: info.signer_org_id,
|
||||
maybe_signer_country: info.signer_country,
|
||||
maybe_cert_serial: info.cert_serial,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_veo(info: VeoInfo) -> VeoVideoInfo {
|
||||
VeoVideoInfo {
|
||||
producer: info.producer,
|
||||
has_c2pa_manifest: info.has_c2pa_manifest,
|
||||
maybe_encoder: info.encoder,
|
||||
maybe_created_description: info.created_description,
|
||||
has_synthid_watermark: info.has_synthid_watermark,
|
||||
maybe_synthid_description: info.synthid_description,
|
||||
maybe_digital_source_type: info.digital_source_type,
|
||||
maybe_claim_generator: info.claim_generator,
|
||||
maybe_claim_generator_version: info.claim_generator_version,
|
||||
maybe_manifest_id: info.manifest_id,
|
||||
maybe_instance_id: info.instance_id,
|
||||
maybe_cert_serial: info.cert_serial,
|
||||
maybe_signer_ca: info.signer_ca,
|
||||
is_timestamped: info.is_timestamped,
|
||||
maybe_timestamp_authority: info.timestamp_authority,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_sora(info: SoraInfo) -> SoraVideoInfo {
|
||||
SoraVideoInfo {
|
||||
producer: info.producer,
|
||||
maybe_model_name: info.model_name,
|
||||
maybe_created_description: info.created_description,
|
||||
maybe_digital_source_type: info.digital_source_type,
|
||||
maybe_claim_generator: info.claim_generator,
|
||||
maybe_manifest_id: info.manifest_id,
|
||||
maybe_instance_id: info.instance_id,
|
||||
maybe_cert_serial: info.cert_serial,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_dreamina(info: DreaminaInfo) -> DreaminaVideoInfo {
|
||||
DreaminaVideoInfo {
|
||||
product: info.product,
|
||||
maybe_export_type: info.export_type,
|
||||
maybe_os: info.os,
|
||||
maybe_source_info: info.source_info,
|
||||
maybe_aigc_label_type: info.aigc_label_type,
|
||||
maybe_video_id: info.video_id,
|
||||
has_c2pa: info.has_c2pa,
|
||||
maybe_signer_org_id: info.signer_org_id,
|
||||
maybe_signer_country: info.signer_country,
|
||||
maybe_cert_serial: info.cert_serial,
|
||||
}
|
||||
}
|
||||
|
||||
fn to_kling(info: KlingInfo) -> KlingVideoInfo {
|
||||
KlingVideoInfo {
|
||||
maybe_model_version: info.model_version,
|
||||
is_ai_generated: info.is_ai_generated,
|
||||
maybe_label: info.label,
|
||||
maybe_content_producer: info.content_producer,
|
||||
maybe_produce_id: info.produce_id,
|
||||
maybe_content_propagator: info.content_propagator,
|
||||
maybe_propagate_id: info.propagate_id,
|
||||
maybe_reserved_code_1: info.reserved_code_1,
|
||||
maybe_reserved_code_2: info.reserved_code_2,
|
||||
has_stream_watermark: info.has_stream_watermark,
|
||||
maybe_watermark_uuid: info.watermark_uuid,
|
||||
}
|
||||
}
|
||||
+2
@@ -21,6 +21,7 @@ use crate::http_server::routes::application_routes::user_bookmarks_routes::add_u
|
||||
use crate::http_server::routes::application_routes::user_rating_routes::add_user_rating_routes;
|
||||
use crate::http_server::routes::application_routes::user_referral_code_routes::add_user_referral_code_routes;
|
||||
use crate::http_server::routes::application_routes::user_routes::add_user_routes;
|
||||
use crate::http_server::routes::application_routes::video_info_routes::add_video_info_routes;
|
||||
use crate::http_server::routes::application_routes::wallet_routes::add_wallet_routes;
|
||||
use crate::http_server::routes::application_routes::web_referrals_routes::add_web_referrals_routes;
|
||||
use crate::http_server::routes::application_routes::webhook_routes::add_webhook_routes;
|
||||
@@ -68,6 +69,7 @@ where
|
||||
app = add_job_routes(app);
|
||||
|
||||
// Other useful tools
|
||||
app = add_video_info_routes(app); // /v1/video_info/... (video provenance detection)
|
||||
app = add_analytics_routes(app); // /v1/analytics/...
|
||||
app = add_web_referrals_routes(app); // /v1/web_referrals/...
|
||||
app = add_dev_routes(app); // /v1/dev/... — dev-only error/response shape probes
|
||||
|
||||
@@ -21,6 +21,7 @@ mod user_bookmarks_routes;
|
||||
mod user_rating_routes;
|
||||
mod user_referral_code_routes;
|
||||
mod user_routes;
|
||||
mod video_info_routes;
|
||||
mod wallet_routes;
|
||||
mod web_referrals_routes;
|
||||
mod webhook_routes;
|
||||
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
use actix_http::body::MessageBody;
|
||||
use actix_service::ServiceFactory;
|
||||
use actix_web::dev::{ServiceRequest, ServiceResponse};
|
||||
use actix_web::error::Error;
|
||||
use actix_web::{web, App, HttpResponse};
|
||||
|
||||
use crate::http_server::endpoints::video_info::video_info_read_info_handler::video_info_read_info_handler;
|
||||
|
||||
pub fn add_video_info_routes<T, B> (app: App<T>) -> App<T>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
app.service(web::scope("/v1/video_info")
|
||||
.service(web::resource("/read_only")
|
||||
.route(web::post().to(video_info_read_info_handler))
|
||||
.route(web::head().to(|| HttpResponse::Ok()))
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -160,6 +160,9 @@ pub struct RedisRateLimiters {
|
||||
|
||||
/// For uploading files for voice conversion, face animator, etc.
|
||||
pub file_upload_logged_in: RedisRateLimiter,
|
||||
|
||||
/// IP-based limiter for the read-only video-info upload endpoint. Fails open.
|
||||
pub video_info_read_only: RedisRateLimiter,
|
||||
}
|
||||
|
||||
/// In-memory caches of several types.
|
||||
|
||||
Reference in New Issue
Block a user