Skip to content

Commit 27592b9

Browse files
Revert to local file + upload for WebDAV output
Direct FFmpeg output to WebDAV doesn't work - Nextcloud silently drops chunked transfer encoding uploads. FFmpeg reports success but no file is created. Going back to: FFmpeg -> local file -> upload via WebDAV client. This is reliable and the WebDAV client handles the upload properly. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent b0bfae2 commit 27592b9

File tree

1 file changed

+55
-55
lines changed

1 file changed

+55
-55
lines changed

src/jobs.rs

Lines changed: 55 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -275,38 +275,16 @@ async fn process_job(job: Job) -> Result<()> {
275275
let filter_complex = build_filter_complex(&job.selection)?;
276276
info!("FFmpeg filter: {}", filter_complex);
277277

278-
// Create the output folder on WebDAV if needed (before FFmpeg runs)
279-
// job.output_path is like "processed/filename.mp4"
280-
if let Some(folder_end) = job.output_path.rfind('/') {
281-
let folder = &job.output_path[..folder_end];
282-
if !folder.is_empty() {
283-
info!("Creating WebDAV client to ensure folder exists...");
284-
let dav_client = WebDavClient::new(&job.webdav_config)?;
285-
info!("Ensuring folder exists: {}", folder);
286-
if let Err(e) = dav_client.ensure_folder_exists(folder).await {
287-
warn!("Could not create folder {}: {} (may already exist)", folder, e);
288-
}
289-
}
290-
}
278+
// Local output path for FFmpeg
279+
let local_output_path = format!("{}/output.mp4", temp_dir);
280+
info!("Local output path: {}", local_output_path);
281+
282+
info!("Starting FFmpeg (output to local file)...");
291283

292-
// Build WebDAV URL for direct output from FFmpeg
293-
let output_url = build_webdav_upload_url(&job.webdav_config, &job.output_path);
294-
info!("FFmpeg will output directly to WebDAV: {}", job.output_path);
295-
// Log URL without credentials for debugging
296-
let output_url_safe = output_url.replacen(
297-
&format!("://{}:{}@", encode(&job.webdav_config.username), encode(&job.webdav_config.password)),
298-
"://[credentials]@",
299-
1
300-
);
301-
info!("Output URL (redacted): {}", output_url_safe);
302-
303-
info!("Starting FFmpeg with direct WebDAV output...");
304-
305-
// Run FFmpeg command - output directly to WebDAV
306-
// Use fragmented MP4 with empty_moov so we can stream without seeking
284+
// Run FFmpeg command - output to local file first
307285
let ffmpeg_result = tokio::process::Command::new("ffmpeg")
308286
.arg("-y") // Overwrite output
309-
.arg("-i").arg(&video_url) // Input video
287+
.arg("-i").arg(&video_url) // Input video (streaming from WebDAV)
310288
.arg("-i").arg(bg_image_path) // Background image
311289
.arg("-filter_complex").arg(&filter_complex)
312290
.arg("-map").arg("[outv]")
@@ -315,10 +293,8 @@ async fn process_job(job: Job) -> Result<()> {
315293
.arg("-crf").arg("18")
316294
.arg("-preset").arg("veryfast")
317295
.arg("-threads").arg("0")
318-
.arg("-c:a").arg("aac") // Re-encode audio for fragmented mp4 compatibility
319-
.arg("-movflags").arg("frag_keyframe+empty_moov") // Fragmented MP4 for streaming
320-
.arg("-method").arg("PUT") // Use HTTP PUT for WebDAV
321-
.arg(&output_url)
296+
.arg("-c:a").arg("copy")
297+
.arg(&local_output_path)
322298
.output()
323299
.await;
324300

@@ -335,20 +311,57 @@ async fn process_job(job: Job) -> Result<()> {
335311
}
336312

337313
if output.status.success() {
338-
info!("FFmpeg processing and direct WebDAV upload successful!");
339-
info!("Job {} completed successfully", job.id);
314+
info!("FFmpeg processing successful!");
340315

341-
// Update job to completed via queue URL
342-
if let Some(queue_url) = &job.webdav_config.queue_url {
343-
info!("Updating job status to completed at: {}", queue_url);
344-
match update_job_status_remote(queue_url, &job.id, JobStatus::Completed, None).await {
345-
Ok(_) => info!("Status update successful"),
346-
Err(e) => error!("Status update failed: {}", e),
316+
// Check output file size
317+
match fs::metadata(&local_output_path) {
318+
Ok(meta) => info!("Output file size: {} bytes", meta.len()),
319+
Err(e) => error!("Failed to stat output file: {}", e),
320+
}
321+
322+
// Now upload to WebDAV
323+
info!("Reading output file for upload...");
324+
let output_data = fs::read(&local_output_path)?;
325+
info!("Read {} bytes, uploading to WebDAV...", output_data.len());
326+
327+
let dav_client = WebDavClient::new(&job.webdav_config)?;
328+
329+
// Create the output folder on WebDAV if needed
330+
// job.output_path is like "processed/filename.mp4"
331+
if let Some(folder_end) = job.output_path.rfind('/') {
332+
let folder = &job.output_path[..folder_end];
333+
if !folder.is_empty() {
334+
info!("Ensuring folder exists: {}", folder);
335+
if let Err(e) = dav_client.ensure_folder_exists(folder).await {
336+
warn!("Could not create folder {}: {} (may already exist)", folder, e);
337+
}
338+
}
339+
}
340+
341+
info!("Uploading to: {}", job.output_path);
342+
match dav_client.upload_file(&job.output_path, output_data).await {
343+
Ok(_) => {
344+
info!("Upload successful!");
345+
info!("Job {} completed successfully", job.id);
346+
347+
// Update job to completed via queue URL
348+
if let Some(queue_url) = &job.webdav_config.queue_url {
349+
info!("Updating job status to completed at: {}", queue_url);
350+
match update_job_status_remote(queue_url, &job.id, JobStatus::Completed, None).await {
351+
Ok(_) => info!("Status update successful"),
352+
Err(e) => error!("Status update failed: {}", e),
353+
}
354+
}
355+
}
356+
Err(e) => {
357+
error!("Upload FAILED: {}", e);
358+
if let Some(queue_url) = &job.webdav_config.queue_url {
359+
let _ = update_job_status_remote(queue_url, &job.id, JobStatus::Failed, None).await;
360+
}
347361
}
348362
}
349363
} else {
350364
error!("FFmpeg FAILED with exit code: {}", output.status);
351-
error!("Direct WebDAV output failed - check FFmpeg stderr above for details");
352365

353366
if let Some(queue_url) = &job.webdav_config.queue_url {
354367
let _ = update_job_status_remote(queue_url, &job.id, JobStatus::Failed, None).await;
@@ -423,19 +436,6 @@ fn build_webdav_download_url(config: &WebDavConfig, path: &str) -> String {
423436
)
424437
.replacen("://", &format!("://{}:{}@", encode(&config.username), encode(&config.password)), 1)
425438
}
426-
427-
fn build_webdav_upload_url(config: &WebDavConfig, output_path: &str) -> String {
428-
// Build full WebDAV URL for uploading
429-
// config.url is like: https://cloud.example.com/remote.php/dav/files/user/VideoTest
430-
// output_path is like: processed/filename.mp4
431-
// Result: https://user:pass@cloud.example.com/remote.php/dav/files/user/VideoTest/processed/filename.mp4
432-
433-
let base_url = config.url.trim_end_matches('/');
434-
let full_url = format!("{}/{}", base_url, output_path);
435-
436-
full_url.replacen("://", &format!("://{}:{}@", encode(&config.username), encode(&config.password)), 1)
437-
}
438-
439439
async fn update_job_status_remote(
440440
queue_url: &str,
441441
job_id: &str,

0 commit comments

Comments
 (0)