Zwischendateien in echten Temp Ordner verlagern, Nextcloud Sperrfehler beheben
This commit is contained in:
parent
3c7e77524e
commit
d7d8c41953
1 changed files with 70 additions and 20 deletions
|
|
@ -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() {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue