Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions openapitools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
"spaces": 2,
"generator-cli": {
"version": "7.17.0"
}
}
30 changes: 30 additions & 0 deletions src/commands/upload/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use tokio::sync::mpsc as tokio_mpsc;

use tellers_api_client::apis::accepts_api_key_api as api;
use tellers_api_client::apis::configuration::Configuration;
use tellers_api_client::models::process_assets_request::GenerateProxy;
use tellers_api_client::models::{
AssetUploadRequest, AssetUploadResponse, CreateFolderRequest, ProcessAssetsRequest,
SourceFileInfo,
Expand Down Expand Up @@ -94,6 +95,10 @@ pub struct UploadCmdArgs {
#[arg(long, default_value_t = false)]
pub dry_run: bool,

/// Proxy heights to request from the server after upload (e.g. 720, 1080). Omit for server default; use empty (e.g. --generate-proxy) for none (e.g. when using --local-encoding).
#[arg(long, value_delimiter = ',', num_args = 0.., value_parser = parse_generate_proxy)]
pub generate_proxy: Option<Vec<GenerateProxy>>,

#[arg(long, default_value_t = false)]
pub disable_description_generation: bool,
}
Expand All @@ -111,6 +116,20 @@ enum DownscaleWork {
Passthrough(PathBuf),
}

fn parse_generate_proxy(s: &str) -> Result<GenerateProxy, String> {
match s.trim() {
"360" => Ok(GenerateProxy::Variant360),
"480" => Ok(GenerateProxy::Variant480),
"720" => Ok(GenerateProxy::Variant720),
"1080" => Ok(GenerateProxy::Variant1080),
"2160" => Ok(GenerateProxy::Variant2160),
_ => Err(format!(
"generate_proxy must be one of 360, 480, 720, 1080, 2160, got '{}'",
s
)),
}
}

fn has_extension(file_path: &PathBuf, extensions: &[String]) -> bool {
if extensions.is_empty() {
return true;
Expand Down Expand Up @@ -517,6 +536,7 @@ fn run_upload(args: UploadCmdArgs) -> Result<(), String> {
bearer_header.as_deref(),
&related_umids_per_file,
args.disable_description_generation,
args.generate_proxy.as_ref(),
)
.await;

Expand Down Expand Up @@ -694,6 +714,8 @@ fn run_two_queue_pipeline(
let user_id = user_id.to_string();
let upload_request_id = upload_request_id.to_string();
let disable_description_generation = args.disable_description_generation;
let local_encoding = args.local_encoding;
let generate_proxy = args.generate_proxy.clone();

let block_result = rt.block_on(async move {
// Start render loop inside runtime so tokio::spawn has a current runtime
Expand Down Expand Up @@ -802,6 +824,9 @@ fn run_two_queue_pipeline(
);
preproc_req.generate_time_based_media_description =
Some(!disable_description_generation);
// Use --generate-proxy if set; otherwise when local encoding use empty (no server proxies); else leave unset for server default.
preproc_req.generate_proxy =
generate_proxy.clone().or_else(|| if local_encoding { Some(vec![]) } else { None });
// related_umid_for_master_clip removed in current API; use override_entity_ids if needed
if !file_related_umids.is_empty() {
preproc_req.override_entity_ids = Some(Some(file_related_umids));
Expand Down Expand Up @@ -1020,6 +1045,7 @@ async fn upload_to_presigned_urls(
bearer_opt: Option<&str>,
related_umids_per_file: &[Vec<String>],
disable_description_generation: bool,
generate_proxy: Option<&Vec<GenerateProxy>>,
) -> Result<(), String> {
let http = Arc::new(
reqwest::Client::builder()
Expand Down Expand Up @@ -1055,6 +1081,7 @@ async fn upload_to_presigned_urls(
let cfg_clone = cfg.clone();
let api_key_clone = api_key.clone();
let bearer_clone = bearer_opt.clone();
let generate_proxy_clone = generate_proxy.cloned();

let file_name = file_path
.file_name()
Expand Down Expand Up @@ -1101,6 +1128,9 @@ async fn upload_to_presigned_urls(
);
preproc_req.generate_time_based_media_description =
Some(!disable_description_generation);
if let Some(proxy) = generate_proxy_clone.as_ref() {
preproc_req.generate_proxy = Some(proxy.clone());
}
if !file_related_umids.is_empty() {
preproc_req.override_entity_ids = Some(Some(file_related_umids));
}
Expand Down
43 changes: 42 additions & 1 deletion src/media/transcode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,35 @@ use ffmpeg_sidecar::command::FfmpegCommand;
use ffmpeg_sidecar::event::{FfmpegEvent, LogLevel};
use std::path::PathBuf;

use crate::media::metadata::get_ffprobe_json;
use crate::media::video_quality::VideoQuality;

/// Returns true if the first video stream is interlaced (field_order is tb, bt, tt, or bb).
fn is_interlaced(path: &PathBuf) -> Result<bool, String> {
let probe = match get_ffprobe_json(path)? {
Some(p) => p,
None => return Ok(false),
};
let streams = probe
.get("streams")
.and_then(|s| s.as_array())
.ok_or_else(|| "no streams in ffprobe output".to_string())?;
let video_stream = streams
.iter()
.find(|s| s.get("codec_type").and_then(|c| c.as_str()) == Some("video"))
.ok_or_else(|| "no video stream".to_string())?;
let field_order = video_stream
.get("field_order")
.and_then(|v| v.as_str())
.unwrap_or("unknown")
.to_lowercase();
// Interlaced: tb, bt, tt, bb. Progressive or unknown => not interlaced.
Ok(matches!(
field_order.as_str(),
"tb" | "bt" | "tt" | "bb"
))
}

/// Parse ffmpeg time string e.g. "00:01:23.45" into seconds.
fn parse_ffmpeg_time(s: &str) -> Option<f64> {
let s = s.trim();
Expand Down Expand Up @@ -123,6 +150,13 @@ pub fn create_rendition(
}
}

let interlaced = is_interlaced(input).unwrap_or(false);
if interlaced {
if let Some(f) = info_cb {
f("Input is interlaced; deinterlacing to progressive");
}
}

let mut cmd = FfmpegCommand::new();
cmd.overwrite()
.input(input.to_string_lossy())
Expand All @@ -131,7 +165,14 @@ pub fn create_rendition(

if let Some(q) = definition.quality {
let height = q.height();
cmd.args(["-vf", &format!("scale=-2:{}", height)]);
let vf = if interlaced {
format!("bwdif=0:-1:0,scale=-2:{}", height)
} else {
format!("scale=-2:{}", height)
};
cmd.args(["-vf", &vf]);
} else if interlaced {
cmd.args(["-vf", "bwdif=0:-1:0"]);
}
if let Some(p) = definition.preset {
cmd.preset(p.as_str());
Expand Down
17 changes: 13 additions & 4 deletions src/tellers_api/openapi.tellers_public_api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1558,9 +1558,6 @@ components:
maintenance_mode:
type: boolean
title: Maintenance Mode
hd_enabled:
type: boolean
title: Hd Enabled
available_agent_tools:
items:
additionalProperties: true
Expand All @@ -1575,7 +1572,6 @@ components:
type: object
required:
- maintenance_mode
- hd_enabled
- available_agent_tools
- available_llm_models
title: AppSettings
Expand Down Expand Up @@ -1949,6 +1945,19 @@ components:
type: array
- type: 'null'
title: Override Entity Ids
generate_proxy:
items:
type: integer
enum:
- 360
- 480
- 720
- 1080
- 2160
type: array
title: Generate Proxy
default:
- 720
type: object
required:
- assets
Expand Down
Loading