diff --git a/src/cli.rs b/src/cli.rs index 2cae383..87585e1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,7 +1,6 @@ use clap::{Parser, Subcommand}; use crate::commands::asset::AssetArgs; -use crate::commands::group::GroupArgs; use crate::commands::upload::UploadArgs; #[derive(Parser, Debug)] @@ -23,7 +22,6 @@ pub struct Cli { #[derive(Subcommand, Debug)] pub enum Command { Asset(AssetArgs), - Group(GroupArgs), Upload(UploadArgs), } diff --git a/src/commands/group/list.rs b/src/commands/group/list.rs deleted file mode 100644 index a11c491..0000000 --- a/src/commands/group/list.rs +++ /dev/null @@ -1,60 +0,0 @@ -use clap::Args; -use tellers_api_client::apis::accepts_api_key_api as api; - -use crate::commands::api_config; -use crate::output; - -#[derive(Args, Debug)] -pub struct ListArgs { - #[arg(long, env = "TELLERS_API_KEY")] - pub api_key: Option, - - #[arg(long, env = "TELLERS_AUTH_BEARER")] - pub auth_bearer: Option, -} - -pub fn run(args: ListArgs) -> Result<(), String> { - let cfg = api_config::create_config(); - let api_key = api_config::get_api_key(args.api_key)?; - let bearer_header = api_config::get_bearer_header(args.auth_bearer); - - tokio::runtime::Runtime::new() - .map_err(|e| format!("failed to start runtime: {}", e))? - .block_on(async move { - let groups = api::list_groups_group_list_get( - &cfg, - Some(&api_key), - bearer_header.as_deref(), - ) - .await - .map_err(|e| { - let mut m = format!("failed to list groups: {}", e); - match &e { - tellers_api_client::apis::Error::Reqwest(req_err) => { - if let Some(status) = req_err.status() { - m.push_str(&format!("; http_status: {}", status)); - } - } - tellers_api_client::apis::Error::ResponseError(resp) => { - m.push_str(&format!("; http_status: {}", resp.status)); - if !resp.content.is_empty() { - m.push_str(&format!("; response_body: {}", resp.content)); - } - } - _ => {} - } - m - })?; - - output::info(format!("Retrieved {} group(s)", groups.len())); - - if groups.is_empty() { - output::info("No groups found"); - } else { - println!("{}", serde_json::to_string_pretty(&groups).unwrap_or_default()); - } - - Ok(()) - }) -} - diff --git a/src/commands/group/mod.rs b/src/commands/group/mod.rs deleted file mode 100644 index 1ebe163..0000000 --- a/src/commands/group/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -use clap::{Args, Subcommand}; - -mod list; - -pub use list::{run as list_run, ListArgs}; - -#[derive(Args, Debug)] -pub struct GroupArgs { - #[command(subcommand)] - pub command: GroupCommand, -} - -#[derive(Subcommand, Debug)] -pub enum GroupCommand { - List(ListArgs), -} - diff --git a/src/commands/mod.rs b/src/commands/mod.rs index c033d54..18b7ee7 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,5 +1,4 @@ pub mod api_config; pub mod asset; -pub mod group; pub mod prompt; pub mod upload; diff --git a/src/commands/upload/main.rs b/src/commands/upload/main.rs index f371ed9..a9d562e 100644 --- a/src/commands/upload/main.rs +++ b/src/commands/upload/main.rs @@ -393,6 +393,7 @@ fn run_upload(args: UploadCmdArgs) -> Result<(), String> { } let mut requests: Vec = Vec::with_capacity(files_to_upload.len()); + let mut related_umids_per_file: Vec> = Vec::with_capacity(files_to_upload.len()); let mut file_upload_ids: Vec = Vec::with_capacity(files_to_upload.len()); let mut file_in_app_paths: Vec = Vec::with_capacity(files_to_upload.len()); let mut upload_paths: Vec = Vec::with_capacity(files_to_upload.len()); @@ -445,14 +446,18 @@ fn run_upload(args: UploadCmdArgs) -> Result<(), String> { vec![], ); - if let Some(metadata) = umid { - if let Some(umid_value) = metadata.material_package_umid { - source_info.capture_device_umid = Some(Some(umid_value)); + if let Some(metadata) = umid.as_ref() { + if let Some(umid_value) = metadata.material_package_umid.as_ref() { + source_info.capture_device_umid = Some(Some(umid_value.clone())); } - if let Some(first_umid) = metadata.file_package_umids.first() { - source_info.umid = Some(Some(first_umid.clone())); + if let Some(first_with_data) = metadata.file_package_umids.iter().find(|u| u.has_data) { + source_info.umid = Some(Some(first_with_data.umid.clone())); } } + related_umids_per_file.push( + umid.map(|m| m.file_package_umids.iter().map(|u| u.umid.clone()).collect()) + .unwrap_or_default(), + ); let req = AssetUploadRequest::new( i32::try_from(content_length).unwrap_or(i32::MAX), @@ -515,6 +520,13 @@ fn run_upload(args: UploadCmdArgs) -> Result<(), String> { ); preproc_req.generate_time_based_media_description = Some(!args.disable_description_generation); + let all_related_umids: Vec = related_umids_per_file + .into_iter() + .flat_map(|v| v.into_iter()) + .collect(); + if !all_related_umids.is_empty() { + preproc_req.related_umid_for_master_clip = Some(Some(all_related_umids)); + } let _ = progress_handle.add_info(format!( "Triggering preprocessing for {} asset(s)...", preproc_req.assets.len() @@ -744,6 +756,7 @@ fn run_two_queue_pipeline( let consumer = async move { let mut completed_responses: Vec = Vec::new(); + let mut completed_related_umids: Vec> = Vec::new(); while let Some(file_info) = upload_rx.recv().await { progress_handle.decrement_upload_queued(); progress_handle.pop_upload_pending(); @@ -755,11 +768,12 @@ fn run_two_queue_pipeline( .to_string(); progress_handle.set_upload_current(Some(file_name.clone())); - let (req, _upload_id, in_app_path_str) = build_single_upload_request( - &file_info, - &base_dir_async, - &in_app_path, - )?; + let (req, _upload_id, in_app_path_str, file_related_umids) = + build_single_upload_request( + &file_info, + &base_dir_async, + &in_app_path, + )?; let responses = request_presigned_urls(&cfg, &vec![req], &api_key, bearer_header).await?; let upload_resp = responses @@ -799,6 +813,7 @@ fn run_two_queue_pipeline( )); } completed_responses.push(upload_resp); + completed_related_umids.push(file_related_umids); progress_handle.set_upload_current(None::<&str>); progress_handle.set_upload_current_pct(None); } @@ -810,6 +825,13 @@ fn run_two_queue_pipeline( ); preproc_req.generate_time_based_media_description = Some(!disable_description_generation); + let all_related_umids: Vec = completed_related_umids + .iter() + .flat_map(|v| v.iter().cloned()) + .collect(); + if !all_related_umids.is_empty() { + preproc_req.related_umid_for_master_clip = Some(Some(all_related_umids)); + } let _ = progress_handle.add_info("Triggering preprocessing..."); let preproc_tasks = api::process_assets_users_assets_preprocess_post( &cfg, @@ -847,7 +869,7 @@ fn build_single_upload_request( file_info: &FileToUpload, base_dir: &PathBuf, in_app_path: &Option, -) -> Result<(AssetUploadRequest, String, String), String> { +) -> Result<(AssetUploadRequest, String, String, Vec), String> { let content_length = std::fs::metadata(&file_info.upload_path) .map_err(|e| format!("failed to stat {}: {}", file_info.upload_path.display(), e))? .len(); @@ -877,12 +899,16 @@ fn build_single_upload_request( None, vec![], ); + let related_umids: Vec = umid + .as_ref() + .map(|m| m.file_package_umids.iter().map(|u| u.umid.clone()).collect()) + .unwrap_or_default(); if let Some(metadata) = umid { if let Some(umid_value) = metadata.material_package_umid { source_info.capture_device_umid = Some(Some(umid_value)); } - if let Some(first_umid) = metadata.file_package_umids.first() { - source_info.umid = Some(Some(first_umid.clone())); + if let Some(first_with_data) = metadata.file_package_umids.iter().find(|u| u.has_data) { + source_info.umid = Some(Some(first_with_data.umid.clone())); } } let req = AssetUploadRequest::new( @@ -890,7 +916,7 @@ fn build_single_upload_request( upload_id.clone(), source_info, ); - Ok((req, upload_id, file_in_app_path)) + Ok((req, upload_id, file_in_app_path, related_umids)) } async fn request_presigned_urls( diff --git a/src/main.rs b/src/main.rs index 60a4d75..15c8619 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,16 +27,6 @@ fn main() { } } } - Some(cli::Command::Group(group_args)) => { - match group_args.command { - commands::group::GroupCommand::List(list_args) => { - if let Err(error) = commands::group::list_run(list_args) { - eprintln!("error: {}", error); - std::process::exit(1); - } - } - } - } Some(cli::Command::Upload(upload_args)) => { if let Err(error) = commands::upload::run(upload_args) { eprintln!("error: {}", error); diff --git a/src/media/metadata.rs b/src/media/metadata.rs index 9cc1480..4970694 100644 --- a/src/media/metadata.rs +++ b/src/media/metadata.rs @@ -4,9 +4,18 @@ use std::path::PathBuf; use std::process::Command; use std::sync::OnceLock; +/// A file-package UMID with a flag indicating whether the stream has media data. +#[derive(Clone, Debug)] +pub struct FilePackageUmid { + pub umid: String, + /// True if this UMID came from a stream with actual media data (video/audio); + /// false if from a stream with `codec_type == "data"` (no media data in the stream). + pub has_data: bool, +} + pub struct MediaMetadata { pub material_package_umid: Option, - pub file_package_umids: Vec, + pub file_package_umids: Vec, } pub fn extract_media_metadata(path: &PathBuf) -> Result { @@ -21,7 +30,7 @@ pub fn extract_media_metadata(path: &PathBuf) -> Result { #[derive(Debug, Default)] pub struct MxfUmids { pub material_package_umid: Option, - pub file_package_umids: Vec, + pub file_package_umids: Vec, } fn run_ffprobe_json(path: &PathBuf) -> Result, String> { @@ -98,7 +107,7 @@ fn extract_mxf_umids(path: &PathBuf) -> Result { }; let mut material = None; - let mut file_package_ids: Vec = Vec::new(); + let mut file_package_ids: Vec = Vec::new(); if let Some(format) = payload.get("format") { if let Some(tags) = format.get("tags") { @@ -113,22 +122,23 @@ fn extract_mxf_umids(path: &PathBuf) -> Result { if let Some(streams) = payload.get("streams") { if let Some(streams_array) = streams.as_array() { for stream in streams_array { - // Skip streams with codec_type "data" - // codec_type == "data" means that there is no data in the stream - if let Some(codec_type) = stream.get("codec_type") { - if let Some(codec_type_str) = codec_type.as_str() { - if codec_type_str == "data" { - continue; - } - } - } - + // Collect file_package_umid from all streams (including codec_type "data") + // so they can be sent as related_umid_for_master_clip. Record whether + // the stream has media data (video/audio) or is data-only (codec_type "data"). + let has_data = stream + .get("codec_type") + .and_then(|v| v.as_str()) + .map(|s| s != "data") + .unwrap_or(true); if let Some(tags) = stream.get("tags") { if let Some(tags_obj) = tags.as_object() { if let Some(umid_value) = tags_obj.get("file_package_umid") { if let Some(file_umid) = sanitize_umid(umid_value.as_str()) { - if !file_package_ids.contains(&file_umid) { - file_package_ids.push(file_umid); + if !file_package_ids.iter().any(|u| u.umid == file_umid) { + file_package_ids.push(FilePackageUmid { + umid: file_umid, + has_data, + }); } } } diff --git a/src/tellers_api/openapi.tellers_public_api.yaml b/src/tellers_api/openapi.tellers_public_api.yaml index 33c87e0..a588380 100644 --- a/src/tellers_api/openapi.tellers_public_api.yaml +++ b/src/tellers_api/openapi.tellers_public_api.yaml @@ -878,212 +878,6 @@ paths: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /group/create: - post: - tags: - - accepts-api-key - summary: Create Group - operationId: create_group_group_create_post - parameters: - - name: x-api-key - in: header - required: false - schema: - anyOf: - - type: string - - type: 'null' - title: X-Api-Key - - name: authorization - in: header - required: false - schema: - anyOf: - - type: string - - type: 'null' - title: Authorization - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/CreateGroupRequest' - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/CreateGroupResponse' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /group/list: - get: - tags: - - accepts-api-key - summary: List Groups - operationId: list_groups_group_list_get - parameters: - - name: x-api-key - in: header - required: false - schema: - anyOf: - - type: string - - type: 'null' - title: X-Api-Key - - name: authorization - in: header - required: false - schema: - anyOf: - - type: string - - type: 'null' - title: Authorization - responses: - '200': - description: Successful Response - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/GroupResponse' - title: Response List Groups Group List Get - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /group/update: - post: - tags: - - accepts-api-key - summary: Update Group - operationId: update_group_group_update_post - parameters: - - name: x-api-key - in: header - required: false - schema: - anyOf: - - type: string - - type: 'null' - title: X-Api-Key - - name: authorization - in: header - required: false - schema: - anyOf: - - type: string - - type: 'null' - title: Authorization - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/UpdateGroupRequest' - responses: - '200': - description: Successful Response - content: - application/json: - schema: {} - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /group/users/add: - post: - tags: - - accepts-api-key - summary: Add Group Users - operationId: add_group_users_group_users_add_post - parameters: - - name: x-api-key - in: header - required: false - schema: - anyOf: - - type: string - - type: 'null' - title: X-Api-Key - - name: authorization - in: header - required: false - schema: - anyOf: - - type: string - - type: 'null' - title: Authorization - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/GroupUsersRequest' - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/GroupUsersResponse' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /group/users/remove: - post: - tags: - - accepts-api-key - summary: Remove Group Users - operationId: remove_group_users_group_users_remove_post - parameters: - - name: x-api-key - in: header - required: false - schema: - anyOf: - - type: string - - type: 'null' - title: X-Api-Key - - name: authorization - in: header - required: false - schema: - anyOf: - - type: string - - type: 'null' - title: Authorization - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/GroupUsersRemoveRequest' - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/GroupUsersResponse' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' components: schemas: AgentMessageRequest: @@ -1406,24 +1200,6 @@ components: required: - path title: CreateFolderResponse - CreateGroupRequest: - properties: - name: - type: string - title: Name - type: object - required: - - name - title: CreateGroupRequest - CreateGroupResponse: - properties: - id: - type: string - title: Id - type: object - required: - - id - title: CreateGroupResponse FileReference: properties: file_name: @@ -1472,88 +1248,6 @@ components: - entity - teller_config title: FileType - GroupResponse: - properties: - id: - type: string - title: Id - name: - type: string - title: Name - type: object - required: - - id - - name - title: GroupResponse - GroupRights: - type: string - enum: - - ADMIN - - READ - - WRITE - title: GroupRights - GroupUsersRemoveRequest: - properties: - group_id: - type: string - title: Group Id - user_ids: - items: - type: string - type: array - title: User Ids - default: [] - emails: - items: - type: string - type: array - title: Emails - default: [] - type: object - required: - - group_id - title: GroupUsersRemoveRequest - GroupUsersRequest: - properties: - group_id: - type: string - title: Group Id - user_ids: - items: - type: string - type: array - title: User Ids - default: [] - emails: - items: - type: string - type: array - title: Emails - default: [] - right: - $ref: '#/components/schemas/GroupRights' - default: READ - type: object - required: - - group_id - title: GroupUsersRequest - GroupUsersResponse: - properties: - user_ids: - items: - type: string - type: array - title: User Ids - not_found: - items: - type: string - type: array - title: Not Found - type: object - required: - - user_ids - - not_found - title: GroupUsersResponse HTTPValidationError: properties: detail: @@ -1634,6 +1328,13 @@ components: type: array - type: 'null' title: Override Entity Ids + related_umid_for_master_clip: + anyOf: + - items: + type: string + type: array + - type: 'null' + title: Related Umid For Master Clip type: object required: - assets @@ -1812,25 +1513,6 @@ components: - ended_at - error_message title: TaskResponse - UpdateGroupRequest: - properties: - group_id: - type: string - title: Group Id - name: - anyOf: - - type: string - - type: 'null' - title: Name - is_public_read_access: - anyOf: - - type: boolean - - type: 'null' - title: Is Public Read Access - type: object - required: - - group_id - title: UpdateGroupRequest UseAgentToolRequest: properties: tool_id: