Zwischendateien in echten Temp Ordner verlagern, Nextcloud Sperrfehler beheben

This commit is contained in:
Jürgen Zwiener 2026-08-19 12:41:08 +02:00
parent 3c7e77524e
commit d7d8c41953

View file

@ -6,13 +6,16 @@
// Dateipfade verarbeitet, nie Videoinhalte selbst in den Arbeitsspeicher der // Dateipfade verarbeitet, nie Videoinhalte selbst in den Arbeitsspeicher der
// Oberfläche geladen. // Oberfläche geladen.
// //
// Wichtiger Hinweis für den ersten echten Build: In dieser Entwicklungsumgebung // Alle Zwischenschritte (geschnittenes Video, rohe/entrauschte Tonspur, usw.)
// steht kein Rust Compiler zur Verfügung, dieser Code wurde also nicht kompiliert, // laufen in einem echten temporären Systemordner (std::env::temp_dir()), nicht im
// nur sorgfältig von Hand geprüft. Zwei Stellen bitte beim ersten Testlauf gezielt // Ordner der Eingabedatei. Grund: Bei einem echten Testlauf am 2026-08-19 lag das
// gegenprüfen: 1) die genauen Kommandozeilen-Optionen von `deep-filter` (siehe // Eingabevideo in einem Nextcloud-synchronisierten Ordner, der Sync-Client hat
// `deep-filter --help`, hier als `-o <Zieldatei>` angenommen), 2) ob `auto-editor` // neu entstehende Zwischen-Dateien sofort selbst kurz geöffnet, was zu "Zugriff
// den Flag `--no-open` in der installierten Version so kennt. Beides ist unten mit // verweigert" Fehlern führte, sobald NovaPhonic dieselbe Datei gleich danach
// Kommentaren markiert. // weiterverarbeiten wollte. Der temporäre Arbeitsordner wird bei einem Fehler
// sofort wieder vollständig gelöscht, bei Erfolg bleibt nur das fertige Ergebnis
// bis zum Speichern über den "Speichern unter" Dialog liegen, siehe
// `save_output_as`, das räumt danach ebenfalls auf.
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fs; use std::fs;
@ -47,12 +50,31 @@ fn save_output_as(source_path: String, suggested_name: String) -> Result<String,
Some(dest) => { Some(dest) => {
fs::copy(&source_path, &dest) fs::copy(&source_path, &dest)
.map_err(|e| format!("Konnte Datei nicht speichern: {e}"))?; .map_err(|e| format!("Konnte Datei nicht speichern: {e}"))?;
cleanup_temp_work_dir(&source_path);
Ok(dest.to_string_lossy().to_string()) Ok(dest.to_string_lossy().to_string())
} }
None => Err("abgebrochen".to_string()), None => Err("abgebrochen".to_string()),
} }
} }
// Löscht den temporären Arbeitsordner einer fertigen Ergebnisdatei, nachdem sie
// erfolgreich an ihr endgültiges Ziel kopiert wurde. Sicherheitshalber nur, wenn
// der Pfad wirklich im System-Temp-Ordner liegt und unserem eigenen Namensmuster
// entspricht, damit hier niemals versehentlich ein echter Nutzerordner betroffen
// sein kann.
fn cleanup_temp_work_dir(source_path: &str) {
let path = PathBuf::from(source_path);
let Some(work_dir) = path.parent() else {
return;
};
let Some(folder_name) = work_dir.file_name().and_then(|n| n.to_str()) else {
return;
};
if work_dir.starts_with(std::env::temp_dir()) && folder_name.starts_with("novaphonic-") {
let _ = fs::remove_dir_all(work_dir);
}
}
// Wichtig: Tauri spricht Sidecar Programme zur Laufzeit nur über ihren bloßen // Wichtig: Tauri spricht Sidecar Programme zur Laufzeit nur über ihren bloßen
// Namen an (z.B. "auto-editor"), der Ordnerpfad "binaries/" wird ausschließlich // Namen an (z.B. "auto-editor"), der Ordnerpfad "binaries/" wird ausschließlich
// beim Einrichten in tauri.conf.json (externalBin) und in den Capabilities // beim Einrichten in tauri.conf.json (externalBin) und in den Capabilities
@ -186,13 +208,43 @@ async fn process_video(app: AppHandle, options: ProcessOptions) -> Result<Proces
let stem = input let stem = input
.file_stem() .file_stem()
.and_then(|s| s.to_str()) .and_then(|s| s.to_str())
.unwrap_or("video"); .unwrap_or("video")
let ext = input.extension().and_then(|s| s.to_str()).unwrap_or("mp4"); .to_string();
let parent = input.parent().unwrap_or_else(|| Path::new(".")); let ext = input
.extension()
.and_then(|s| s.to_str())
.unwrap_or("mp4")
.to_string();
// Eigener, garantiert eindeutiger Arbeitsordner im System-Temp-Verzeichnis,
// pro Verarbeitungslauf neu, damit sich mehrere Läufe nie in die Quere kommen.
let work_dir =
std::env::temp_dir().join(format!("novaphonic-{stem}-{}", std::process::id()));
fs::create_dir_all(&work_dir).map_err(|e| e.to_string())?;
match run_pipeline(&app, &options, &input, &work_dir, &stem, &ext).await {
Ok(final_path) => Ok(ProcessResult {
output_path: final_path.to_string_lossy().to_string(),
}),
Err(e) => {
// Bei einem Fehler bleibt nichts Halbfertiges im Temp-Ordner liegen.
let _ = fs::remove_dir_all(&work_dir);
Err(e)
}
}
}
async fn run_pipeline(
app: &AppHandle,
options: &ProcessOptions,
input: &Path,
work_dir: &Path,
stem: &str,
ext: &str,
) -> Result<PathBuf, String> {
// ---------- 1) Schnitt (auto-editor) ---------- // ---------- 1) Schnitt (auto-editor) ----------
emit_progress(&app, "cut", "running", Some("startet…".into()), None); emit_progress(app, "cut", "running", Some("startet…".into()), None);
let cut_path = parent.join(format!("{stem}_geschnitten.{ext}")); let cut_path = work_dir.join(format!("{stem}_geschnitten.{ext}"));
let margin = format!("{}sec", options.margin_seconds); let margin = format!("{}sec", options.margin_seconds);
run_sidecar( run_sidecar(
&app, &app,
@ -222,8 +274,8 @@ async fn process_video(app: AppHandle, options: ProcessOptions) -> Result<Proces
// ---------- 2) Entrauschen (DeepFilterNet), optional ---------- // ---------- 2) Entrauschen (DeepFilterNet), optional ----------
if options.denoise { if options.denoise {
emit_progress(&app, "denoise", "running", Some("startet…".into()), None); emit_progress(&app, "denoise", "running", Some("startet…".into()), None);
let raw_audio = parent.join(format!("{stem}_audio.wav")); let raw_audio = work_dir.join(format!("{stem}_audio.wav"));
let denoised_audio = parent.join(format!("{stem}_entrauscht.wav")); let denoised_audio = work_dir.join(format!("{stem}_entrauscht.wav"));
run_sidecar( run_sidecar(
&app, &app,
@ -254,7 +306,7 @@ async fn process_video(app: AppHandle, options: ProcessOptions) -> Result<Proces
// (per echtem Testlauf bestätigt, 2026-08-19). Deshalb erst in einen eigenen // (per echtem Testlauf bestätigt, 2026-08-19). Deshalb erst in einen eigenen
// Zwischenordner schreiben lassen und die entstandene Datei danach auf den // Zwischenordner schreiben lassen und die entstandene Datei danach auf den
// gewünschten Namen umbenennen. // gewünschten Namen umbenennen.
let denoise_out_dir = parent.join(format!("{stem}_denoise_tmp")); let denoise_out_dir = work_dir.join(format!("{stem}_denoise_tmp"));
fs::create_dir_all(&denoise_out_dir).map_err(|e| e.to_string())?; fs::create_dir_all(&denoise_out_dir).map_err(|e| e.to_string())?;
run_sidecar( run_sidecar(
&app, &app,
@ -288,7 +340,7 @@ async fn process_video(app: AppHandle, options: ProcessOptions) -> Result<Proces
})?; })?;
let _ = fs::remove_dir_all(&denoise_out_dir); let _ = fs::remove_dir_all(&denoise_out_dir);
let remuxed = parent.join(format!("{stem}_entrauscht_video.{ext}")); let remuxed = work_dir.join(format!("{stem}_entrauscht_video.{ext}"));
run_sidecar( run_sidecar(
&app, &app,
"ffmpeg", "ffmpeg",
@ -325,7 +377,7 @@ async fn process_video(app: AppHandle, options: ProcessOptions) -> Result<Proces
} }
// ---------- 3) Lautstärke & Klangbalance (FFmpeg loudnorm), optional ---------- // ---------- 3) Lautstärke & Klangbalance (FFmpeg loudnorm), optional ----------
let final_path = parent.join(format!("{stem}_nova.{ext}")); let final_path = work_dir.join(format!("{stem}_nova.{ext}"));
if options.loudnorm { if options.loudnorm {
emit_progress(&app, "loudnorm", "running", Some("startet…".into()), None); emit_progress(&app, "loudnorm", "running", Some("startet…".into()), None);
// Bewusst Einzelpass-Loudnorm statt klassischem Zweipass-Verfahren: kein // Bewusst Einzelpass-Loudnorm statt klassischem Zweipass-Verfahren: kein
@ -361,9 +413,7 @@ async fn process_video(app: AppHandle, options: ProcessOptions) -> Result<Proces
emit_progress(&app, "loudnorm", "done", Some("übersprungen".into()), None); emit_progress(&app, "loudnorm", "done", Some("übersprungen".into()), None);
} }
Ok(ProcessResult { Ok(final_path)
output_path: final_path.to_string_lossy().to_string(),
})
} }
fn main() { fn main() {