Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
9c743e2
cleanup `upload_video` arguments
oscartbeaumont Sep 27, 2025
a0d7028
wip
oscartbeaumont Sep 27, 2025
8a1d081
wip
oscartbeaumont Sep 28, 2025
9d14a0d
make `progress_upload` required for instant mode
oscartbeaumont Sep 28, 2025
ea2bd20
fix uploading of thumbnails
oscartbeaumont Sep 28, 2025
313d7f1
absolute mess
oscartbeaumont Sep 28, 2025
c813c07
store recordings into project meta?
oscartbeaumont Sep 28, 2025
a65d871
cursed realtime upload progress
oscartbeaumont Sep 28, 2025
7a8bf58
break out API definitions + use tracing for logging in `upload.rs`
oscartbeaumont Sep 28, 2025
ead7d0f
working stream-based uploader
oscartbeaumont Sep 28, 2025
b288365
bring back progress tracking to upload
oscartbeaumont Sep 28, 2025
7a4d428
implement `from_file` abstraction
oscartbeaumont Sep 29, 2025
ecddfb7
abstract another endpoint into `api` module
oscartbeaumont Sep 29, 2025
c5c8e84
abstract more API + retry on S3 upload request
oscartbeaumont Sep 29, 2025
476a649
finish overhauling upload code
oscartbeaumont Sep 29, 2025
c411b02
restructure project file again
oscartbeaumont Sep 29, 2025
cc1d0de
UI for uploading state + errors
oscartbeaumont Sep 29, 2025
8356ec0
show recording and pending status
oscartbeaumont Sep 29, 2025
2da924c
merge in changes from #1077
oscartbeaumont Sep 29, 2025
2806aff
Merge branch 'main' into local-upload-tracking
oscartbeaumont Sep 29, 2025
19c495c
upload progress working in sync pogggg
oscartbeaumont Sep 29, 2025
49b9dc5
wip
oscartbeaumont Sep 29, 2025
c48c57d
polish off todo's in `recordings.tsx`
oscartbeaumont Sep 29, 2025
a101f6d
a bunch of random fixes
oscartbeaumont Sep 29, 2025
7ea039c
fixes
oscartbeaumont Oct 2, 2025
d890931
fix
oscartbeaumont Oct 2, 2025
50aae68
Merge branch 'main' into local-upload-tracking
oscartbeaumont Oct 2, 2025
cc85380
resumable system
oscartbeaumont Oct 2, 2025
1bb11a9
format
oscartbeaumont Oct 2, 2025
9c4f064
feature flag the new uploader
oscartbeaumont Oct 2, 2025
529cc5c
fixes to some remaining issues
oscartbeaumont Oct 2, 2025
b7c38c7
fix button visibility on `CapCard`
oscartbeaumont Oct 2, 2025
a782308
wip
oscartbeaumont Oct 2, 2025
6bccc9d
Clippy improvements
oscartbeaumont Oct 2, 2025
1f88951
cleanup
oscartbeaumont Oct 2, 2025
02edbdf
fix CI
oscartbeaumont Oct 2, 2025
a5e332e
fix Typescript
oscartbeaumont Oct 2, 2025
d4b9b0c
fixes
oscartbeaumont Oct 2, 2025
35ebc8d
make `CapCard` more conservative with upload status
oscartbeaumont Oct 2, 2025
fa894b7
Merge branch 'main' into local-upload-tracking
oscartbeaumont Oct 2, 2025
f05f2e4
CodeRabbit fixes
oscartbeaumont Oct 2, 2025
df98f25
fix some `CapCard` states
oscartbeaumont Oct 2, 2025
2cae777
fix retry policy
oscartbeaumont Oct 2, 2025
46b9f16
wip
oscartbeaumont Oct 2, 2025
f26d5c5
Merge branch 'main' into local-upload-tracking
oscartbeaumont Oct 3, 2025
b5b7e39
Merge branch 'main' into local-upload-tracking
oscartbeaumont Oct 6, 2025
ef47a9d
fixes
oscartbeaumont Oct 6, 2025
8d6cb13
emit first chunk multiple times
oscartbeaumont Oct 6, 2025
f0435a4
potentially fix the missing video toolbar sometimes
oscartbeaumont Oct 6, 2025
58dede1
improve chunk emit logic
oscartbeaumont Oct 6, 2025
30d4726
fix retry policy for s3 upload
oscartbeaumont Oct 6, 2025
13c71cf
stttttrrrriiiinnnnggg
oscartbeaumont Oct 6, 2025
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
32 changes: 32 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions apps/desktop/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ wgpu.workspace = true
bytemuck = "1.23.1"
kameo = "0.17.2"
tauri-plugin-sentry = "0.5.0"
thiserror.workspace = true
bytes = "1.10.1"
async-stream = "0.3.6"

[target.'cfg(target_os = "macos")'.dependencies]
core-graphics = "0.24.0"
Expand Down
244 changes: 244 additions & 0 deletions apps/desktop/src-tauri/src/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
//! TODO: We should investigate generating this with OpenAPI.
//! This will come part of the EffectTS rewrite work.

use serde::{Deserialize, Serialize};
use serde_json::json;
use tauri::AppHandle;

use crate::web_api::ManagerExt;

pub async fn upload_multipart_initiate(app: &AppHandle, video_id: &str) -> Result<String, String> {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Response {
upload_id: String,
}

let resp = app
.authed_api_request("/api/upload/multipart/initiate", |c, url| {
c.post(url)
.header("Content-Type", "application/json")
.json(&serde_json::json!({
"videoId": video_id,
"contentType": "video/mp4"
}))
})
.await
.map_err(|err| format!("api/upload_multipart_initiate/request: {err}"))?;

if !resp.status().is_success() {
let status = resp.status();
let error_body = resp
.text()
.await
.unwrap_or_else(|_| "<no response body>".to_string());
return Err(format!(
"api/upload_multipart_initiate/{status}: {error_body}"
));
}

resp.json::<Response>()
.await
.map_err(|err| format!("api/upload_multipart_initiate/response: {err}"))
.map(|data| data.upload_id)
}

pub async fn upload_multipart_presign_part(
app: &AppHandle,
video_id: &str,
upload_id: &str,
part_number: u32,
md5_sum: &str,
) -> Result<String, String> {
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Response {
presigned_url: String,
}

let resp = app
.authed_api_request("/api/upload/multipart/presign-part", |c, url| {
c.post(url)
.header("Content-Type", "application/json")
.json(&serde_json::json!({
"videoId": video_id,
"uploadId": upload_id,
"partNumber": part_number,
"md5Sum": md5_sum
}))
})
.await
.map_err(|err| format!("api/upload_multipart_presign_part/request: {err}"))?;

if !resp.status().is_success() {
let status = resp.status();
let error_body = resp
.text()
.await
.unwrap_or_else(|_| "<no response body>".to_string());
return Err(format!(
"api/upload_multipart_presign_part/{status}: {error_body}"
));
}

resp.json::<Response>()
.await
.map_err(|err| format!("api/upload_multipart_presign_part/response: {err}"))
.map(|data| data.presigned_url)
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct UploadedPart {
pub part_number: u32,
pub etag: String,
pub size: usize,
#[serde(skip)]
pub total_size: u64,
}

#[derive(Serialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct S3VideoMeta {
#[serde(rename = "durationInSecs")]
pub duration_in_secs: f64,
pub width: u32,
pub height: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub fps: Option<f32>,
}

pub async fn upload_multipart_complete(
app: &AppHandle,
video_id: &str,
upload_id: &str,
parts: &[UploadedPart],
meta: Option<S3VideoMeta>,
) -> Result<Option<String>, String> {
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MultipartCompleteRequest<'a> {
video_id: &'a str,
upload_id: &'a str,
parts: &'a [UploadedPart],
#[serde(flatten)]
meta: Option<S3VideoMeta>,
}

#[derive(Deserialize)]
pub struct Response {
location: Option<String>,
}

let resp = app
.authed_api_request("/api/upload/multipart/complete", |c, url| {
c.post(url)
.header("Content-Type", "application/json")
.json(&MultipartCompleteRequest {
video_id,
upload_id,
parts,
meta,
})
})
.await
.map_err(|err| format!("api/upload_multipart_complete/request: {err}"))?;

if !resp.status().is_success() {
let status = resp.status();
let error_body = resp
.text()
.await
.unwrap_or_else(|_| "<no response body>".to_string());
return Err(format!(
"api/upload_multipart_complete/{status}: {error_body}"
));
}

resp.json::<Response>()
.await
.map_err(|err| format!("api/upload_multipart_complete/response: {err}"))
.map(|data| data.location)
}

#[derive(Serialize)]
#[serde(rename_all = "lowercase")]
pub enum PresignedS3PutRequestMethod {
#[allow(unused)]
Post,
Put,
}

#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct PresignedS3PutRequest {
pub video_id: String,
pub subpath: String,
pub method: PresignedS3PutRequestMethod,
#[serde(flatten)]
pub meta: Option<S3VideoMeta>,
}

pub async fn upload_signed(app: &AppHandle, body: PresignedS3PutRequest) -> Result<String, String> {
#[derive(Deserialize)]
struct Data {
url: String,
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Response {
presigned_put_data: Data,
}

let resp = app
.authed_api_request("/api/upload/signed", |client, url| {
client.post(url).json(&body)
})
.await
.map_err(|err| format!("api/upload_signed/request: {err}"))?;

if !resp.status().is_success() {
let status = resp.status();
let error_body = resp
.text()
.await
.unwrap_or_else(|_| "<no response body>".to_string());
return Err(format!("api/upload_signed/{status}: {error_body}"));
}

resp.json::<Response>()
.await
.map_err(|err| format!("api/upload_signed/response: {err}"))
.map(|data| data.presigned_put_data.url)
}

pub async fn desktop_video_progress(
app: &AppHandle,
video_id: &str,
uploaded: u64,
total: u64,
) -> Result<(), String> {
let resp = app
.authed_api_request("/api/desktop/video/progress", |client, url| {
client.post(url).json(&json!({
"videoId": video_id,
"uploaded": uploaded,
"total": total,
"updatedAt": chrono::Utc::now().to_rfc3339()
}))
})
.await
.map_err(|err| format!("api/desktop_video_progress/request: {err}"))?;

if !resp.status().is_success() {
let status = resp.status();
let error_body = resp
.text()
.await
.unwrap_or_else(|_| "<no response body>".to_string());
return Err(format!("api/desktop_video_progress/{status}: {error_body}"));
}

Ok(())
}
7 changes: 7 additions & 0 deletions apps/desktop/src-tauri/src/general_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ pub struct GeneralSettingsStore {
pub enable_new_recording_flow: bool,
#[serde(default)]
pub post_deletion_behaviour: PostDeletionBehaviour,
#[serde(default = "default_enable_new_uploader", skip_serializing_if = "no")]
pub enable_new_uploader: bool,
}

fn default_enable_native_camera_preview() -> bool {
Expand All @@ -108,6 +110,10 @@ fn default_enable_new_recording_flow() -> bool {
cfg!(debug_assertions)
}

fn default_enable_new_uploader() -> bool {
cfg!(debug_assertions)
}

fn no(_: &bool) -> bool {
false
}
Expand Down Expand Up @@ -155,6 +161,7 @@ impl Default for GeneralSettingsStore {
auto_zoom_on_clicks: false,
enable_new_recording_flow: default_enable_new_recording_flow(),
post_deletion_behaviour: PostDeletionBehaviour::DoNothing,
enable_new_uploader: default_enable_new_uploader(),
}
}
}
Expand Down
Loading
Loading