Skip to content

Commit e6dd17b

Browse files
authored
Add export command (#30)
* Add export route * Add missing openapi
1 parent 8832451 commit e6dd17b

8 files changed

Lines changed: 481 additions & 0 deletions

File tree

src/cli.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use clap::{Parser, Subcommand};
22

33
use crate::commands::asset::AssetArgs;
4+
use crate::commands::project::ProjectArgs;
45
use crate::commands::upload::UploadArgs;
56

67
#[derive(Parser, Debug)]
@@ -42,6 +43,7 @@ pub struct Cli {
4243
#[derive(Subcommand, Debug)]
4344
pub enum Command {
4445
Asset(AssetArgs),
46+
Project(ProjectArgs),
4547
Upload(UploadArgs),
4648
}
4749

src/commands/asset/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
use clap::{Args, Subcommand};
22

33
mod list;
4+
mod set_anonymous_read;
45

56
pub use list::{run as list_run, ListArgs};
7+
pub use set_anonymous_read::{run as set_anonymous_read_run, SetAnonymousReadArgs};
68

79
#[derive(Args, Debug)]
810
pub struct AssetArgs {
@@ -13,5 +15,7 @@ pub struct AssetArgs {
1315
#[derive(Subcommand, Debug)]
1416
pub enum AssetCommand {
1517
List(ListArgs),
18+
/// Set anonymous read permission for an asset (allow unauthenticated read).
19+
SetAnonymousRead(SetAnonymousReadArgs),
1620
}
1721

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
use clap::Args;
2+
use tellers_api_client::models::AssetVisibilityRequest;
3+
4+
use crate::commands::api_config;
5+
6+
#[derive(Args, Debug)]
7+
pub struct SetAnonymousReadArgs {
8+
/// Asset ID to set anonymous read for
9+
pub asset_id: String,
10+
11+
/// Set anonymous read to true or false (default: true).
12+
#[arg(long, default_value_t = true, value_parser = clap::value_parser!(bool))]
13+
pub allow: bool,
14+
15+
#[arg(long, env = "TELLERS_API_KEY", hide = true)]
16+
pub api_key: Option<String>,
17+
18+
#[arg(long, env = "TELLERS_AUTH_BEARER", hide = true)]
19+
pub auth_bearer: Option<String>,
20+
}
21+
22+
pub fn run(args: SetAnonymousReadArgs) -> Result<(), String> {
23+
let base = api_config::get_api_base();
24+
let api_key = api_config::get_api_key(args.api_key)?;
25+
let bearer = api_config::get_bearer_header(args.auth_bearer);
26+
27+
let url = format!(
28+
"{}/asset/{}/visibility",
29+
base.trim_end_matches('/'),
30+
args.asset_id
31+
);
32+
let body = AssetVisibilityRequest::new(args.allow);
33+
34+
tokio::runtime::Runtime::new()
35+
.map_err(|e| format!("failed to start runtime: {}", e))?
36+
.block_on(async move {
37+
let client = reqwest::Client::new();
38+
let mut req = client
39+
.put(&url)
40+
.json(&body)
41+
.header("x-api-key", &api_key);
42+
if let Some(ref b) = bearer {
43+
req = req.header("authorization", b);
44+
}
45+
let resp = req.send().await.map_err(|e| format!("request failed: {}", e))?;
46+
let status = resp.status();
47+
if !status.is_success() {
48+
let text = resp.text().await.unwrap_or_default();
49+
return Err(format!(
50+
"set_anonymous_read failed; http_status: {}; response: {}",
51+
status, text
52+
));
53+
}
54+
println!(
55+
"anonymous_read set to {} for asset {}",
56+
args.allow, args.asset_id
57+
);
58+
Ok(())
59+
})
60+
}

src/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod api_config;
22
pub mod asset;
33
pub mod prompt;
4+
pub mod project;
45
pub mod upload;

src/commands/project/export.rs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use clap::Args;
2+
use tellers_api_client::apis::accepts_api_key_api as api;
3+
4+
use crate::commands::api_config;
5+
6+
#[derive(Args, Debug)]
7+
pub struct ExportArgs {
8+
/// Project ID to export
9+
#[arg(value_name = "PROJECT_ID")]
10+
pub project_id: String,
11+
12+
/// Renditions to export (360p, 480p, 720p, 1080p, 1440p, 4k). Default 1080p when omitted.
13+
#[arg(long = "rendition", short = 'r', value_name = "RESOLUTION")]
14+
pub renditions: Vec<String>,
15+
16+
#[arg(long, env = "TELLERS_API_KEY")]
17+
pub api_key: Option<String>,
18+
19+
#[arg(long, env = "TELLERS_AUTH_BEARER")]
20+
pub auth_bearer: Option<String>,
21+
}
22+
23+
const ALLOWED_RENDITIONS: &[&str] = &["360p", "480p", "720p", "1080p", "1440p", "4k"];
24+
25+
fn parse_rendition(s: &str) -> Result<String, String> {
26+
let v = s.trim().to_lowercase();
27+
if ALLOWED_RENDITIONS.contains(&v.as_str()) {
28+
Ok(v)
29+
} else {
30+
Err(format!(
31+
"Invalid rendition '{}'; allowed: {}",
32+
s,
33+
ALLOWED_RENDITIONS.join(", ")
34+
))
35+
}
36+
}
37+
38+
pub fn run(args: ExportArgs) -> Result<(), String> {
39+
let cfg = api_config::create_config();
40+
let api_key = api_config::get_api_key(args.api_key)?;
41+
let bearer_header = api_config::get_bearer_header(args.auth_bearer);
42+
43+
let rendition_strs: Vec<String> = if args.renditions.is_empty() {
44+
vec!["1080p".to_string()]
45+
} else {
46+
args.renditions
47+
.iter()
48+
.flat_map(|s| s.split(',').map(|s| s.trim().to_string()))
49+
.filter(|s| !s.is_empty())
50+
.collect()
51+
};
52+
53+
let renditions: Vec<String> = rendition_strs
54+
.iter()
55+
.map(|s| parse_rendition(s))
56+
.collect::<Result<Vec<_>, _>>()?;
57+
58+
tokio::runtime::Runtime::new()
59+
.map_err(|e| format!("failed to start runtime: {}", e))?
60+
.block_on(async move {
61+
let resp = api::export_project_project_project_id_export_post(
62+
&cfg,
63+
&args.project_id,
64+
renditions,
65+
Some(&api_key),
66+
bearer_header.as_deref(),
67+
)
68+
.await
69+
.map_err(|e| {
70+
let mut m = format!("export failed: {}", e);
71+
match &e {
72+
tellers_api_client::apis::Error::Reqwest(req_err) => {
73+
if let Some(status) = req_err.status() {
74+
m.push_str(&format!("; http_status: {}", status));
75+
}
76+
}
77+
tellers_api_client::apis::Error::ResponseError(resp) => {
78+
m.push_str(&format!("; http_status: {}", resp.status));
79+
if !resp.content.is_empty() {
80+
m.push_str(&format!("; response: {}", resp.content));
81+
}
82+
}
83+
_ => {}
84+
}
85+
m
86+
})?;
87+
88+
println!("task_id: {}", resp.task_id);
89+
println!("asset_id: {}", resp.asset_id);
90+
Ok(())
91+
})
92+
}

src/commands/project/mod.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use clap::{Args, Subcommand};
2+
3+
mod export;
4+
5+
pub use export::{run as export_run, ExportArgs};
6+
7+
#[derive(Args, Debug)]
8+
pub struct ProjectArgs {
9+
#[command(subcommand)]
10+
pub command: ProjectCommand,
11+
}
12+
13+
#[derive(Subcommand, Debug)]
14+
pub enum ProjectCommand {
15+
/// Export project to MP4 at one or more resolutions (optionally into a project folder).
16+
Export(ExportArgs),
17+
}

src/main.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@ fn main() {
2525
std::process::exit(1);
2626
}
2727
}
28+
commands::asset::AssetCommand::SetAnonymousRead(args) => {
29+
if let Err(error) = commands::asset::set_anonymous_read_run(args) {
30+
eprintln!("error: {}", error);
31+
std::process::exit(1);
32+
}
33+
}
34+
}
35+
}
36+
Some(cli::Command::Project(project_args)) => {
37+
match project_args.command {
38+
commands::project::ProjectCommand::Export(export_args) => {
39+
if let Err(error) = commands::project::export_run(export_args) {
40+
eprintln!("error: {}", error);
41+
std::process::exit(1);
42+
}
43+
}
2844
}
2945
}
3046
Some(cli::Command::Upload(upload_args)) => {

0 commit comments

Comments
 (0)