From f4e7ce6dff1cd6bf238f598fa42c6b5fb2017fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Zwiener?= Date: Sat, 29 Aug 2026 20:52:33 +0200 Subject: [PATCH] Standardspeicherziel eingebaut, echte Changelog Liste im Release, build.yml Stand repariert --- .github/workflows/build.yml | 38 +++++++++--- app/app.js | 85 +++++++++++++++++++++++++- app/index.html | 12 ++++ tauri-app/package.json | 2 +- tauri-app/src-tauri/Cargo.toml | 2 +- tauri-app/src-tauri/src/main.rs | 95 ++++++++++++++++++++++++++++- tauri-app/src-tauri/tauri.conf.json | 2 +- 7 files changed, 221 insertions(+), 15 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c6c0fa0..f573a53 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,6 +22,8 @@ jobs: steps: - name: Code auschecken uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Node.js einrichten uses: actions/setup-node@v4 @@ -43,10 +45,6 @@ jobs: New-Item -ItemType Directory -Force -Path $binDir | Out-Null $triple = "x86_64-pc-windows-msvc" - # Prueft, dass eine heruntergeladene Datei tatsaechlich ein echtes Windows - # Programm ist, statt z.B. einer kleinen, fehlgeschlagenen Downloadantwort - # (Fehlerseite, abgebrochene Uebertragung), die sonst unbemerkt als exe - # eingepackt wuerde und erst beim Nutzer mit "os error" fehlschlaegt. function Assert-ValidExe($path) { if (-not (Test-Path $path)) { Write-Error "$path wurde nicht angelegt, Download ist fehlgeschlagen." @@ -79,9 +77,6 @@ jobs: Write-Host "Ermittle aktuelle DeepFilterNet Windows Datei..." $release = Invoke-RestMethod -Uri "https://api.github.com/repos/Rikorose/DeepFilterNet/releases/latest" -Headers @{ "User-Agent" = "novaphonic-build" } - # Bewusst "windows" statt nur "win" im Muster: "win" allein steckt zufaellig - # auch im Wort "darwin" (Apple macOS), das hatte vorher versehentlich die - # falsche, nicht-Windows-Datei ausgewaehlt. $winAsset = $release.assets | Where-Object { $_.name -match "(?i)deep-filter-.*x86_64-pc-windows-msvc\.exe$" } | Select-Object -First 1 if (-not $winAsset) { Write-Error "Kein passendes DeepFilterNet Windows Asset in der aktuellen Release gefunden. Bitte https://github.com/Rikorose/DeepFilterNet/releases manuell pruefen und diesen Schritt anpassen." @@ -110,6 +105,27 @@ jobs: path: | tauri-app/src-tauri/target/release/bundle/nsis/*.exe + - name: Änderungsliste seit dem letzten Tag ermitteln (nur bei Versions-Tag) + if: startsWith(github.ref, 'refs/tags/v') + id: changelog + shell: bash + run: | + set -e + CURRENT_TAG="${GITHUB_REF_NAME}" + PREV_TAG=$(git tag --sort=-v:refname | grep -v "^${CURRENT_TAG}$" | head -n 1) + echo "Aktueller Tag: ${CURRENT_TAG}" + echo "Vorheriger Tag: ${PREV_TAG:-keiner, es ist das erste Release}" + { + echo "changelog<> "$GITHUB_OUTPUT" + - name: Bauen, signieren und GitHub Release veröffentlichen (nur bei Versions-Tag) if: startsWith(github.ref, 'refs/tags/v') uses: tauri-apps/tauri-action@v0 @@ -119,8 +135,12 @@ jobs: projectPath: tauri-app tagName: ${{ github.ref_name }} releaseName: 'NovaPhonic ${{ github.ref_name }}' - releaseBody: 'NovaPhonic ${{ github.ref_name }}. Enthält den Windows Installer sowie das Update Manifest für die In App Aktualisierung.' + releaseBody: | + NovaPhonic ${{ github.ref_name }}. Enthält den Windows Installer sowie das Update Manifest für die In App Aktualisierung. + + Änderungen in diesem Release: + ${{ steps.changelog.outputs.changelog }} releaseDraft: false prerelease: false includeUpdaterJson: true - generateReleaseNotes: true + generateReleaseNotes: false diff --git a/app/app.js b/app/app.js index 3858a58..4ab6db8 100644 --- a/app/app.js +++ b/app/app.js @@ -148,6 +148,65 @@ loudnormToggle.addEventListener('change', syncLoudnormOptions); syncLoudnormOptions(); + // ---------- Automatisches Speichern ---------- + // Standard bleibt der "Speichern unter" Dialog nach jeder Verarbeitung (wie + // bisher). Wer die Checkbox aktiviert, bekommt stattdessen jedes Ergebnis + // automatisch in einem einmal gewählten Ordner abgelegt, ohne Dialog. Die + // Einstellung wird über zwei kleine Rust Commands (load_settings/save_settings) + // in einer settings Datei im App Konfigurationsordner gespeichert, damit sie + // App-Neustarts übersteht. + const autoSaveToggle = $('autoSaveToggle'); + const autoSaveFolderRow = $('autoSaveFolderRow'); + const autoSaveFolderPath = $('autoSaveFolderPath'); + const chooseAutoSaveFolderBtn = $('chooseAutoSaveFolderBtn'); + let appSettings = { autoSaveEnabled: false, autoSaveFolder: null }; + + function syncAutoSaveUi() { + autoSaveFolderRow.style.display = autoSaveToggle.checked ? 'block' : 'none'; + autoSaveFolderPath.textContent = appSettings.autoSaveFolder || 'Kein Ordner ausgewählt'; + } + + async function persistSettings() { + appSettings.autoSaveEnabled = autoSaveToggle.checked; + if (!isTauri()) return; + try { + await window.__TAURI__.core.invoke('save_settings', { settings: appSettings }); + } catch (e) { + console.error(e); + } + } + + autoSaveToggle.addEventListener('change', () => { + syncAutoSaveUi(); + persistSettings(); + }); + + chooseAutoSaveFolderBtn.addEventListener('click', async () => { + if (!isTauri()) return; + try { + const folder = await window.__TAURI__.core.invoke('pick_save_folder'); + if (folder) { + appSettings.autoSaveFolder = folder; + syncAutoSaveUi(); + persistSettings(); + } + } catch (e) { + console.error(e); + } + }); + + async function loadSettings() { + if (!isTauri()) { syncAutoSaveUi(); return; } + try { + const loaded = await window.__TAURI__.core.invoke('load_settings'); + if (loaded) appSettings = loaded; + autoSaveToggle.checked = !!appSettings.autoSaveEnabled; + } catch (e) { + console.error(e); + } + syncAutoSaveUi(); + } + // ---------- Pipeline Ablauf ---------- const STEP_IDS = ['denoise', 'cut', 'loudnorm']; @@ -215,8 +274,31 @@ } }); - function showResult(result) { + async function showResult(result) { const name = result.outputPath.split(/[\\/]/).pop(); + + // Automatisches Speichern: wenn aktiviert und ein Ordner gewählt wurde, + // landet das Ergebnis sofort dort, ohne "Speichern unter" Dialog. + if (appSettings.autoSaveEnabled && appSettings.autoSaveFolder) { + try { + const saved = await window.__TAURI__.core.invoke('auto_save_output', { + sourcePath: result.outputPath, + suggestedName: name, + targetFolder: appSettings.autoSaveFolder + }); + $('resultArea').innerHTML = + '
' + name.replace(/
' + + '
Automatisch gespeichert unter: ' + + String(saved).replace(/'; + return; + } catch (e) { + console.error(e); + // Bei einem Fehler beim automatischen Speichern (z.B. Ordner nicht mehr + // vorhanden) auf den manuellen Weg zurückfallen, damit das fertige + // Ergebnis nicht verloren geht. + } + } + $('resultArea').innerHTML = '
' + '' + name.replace(/' + @@ -304,5 +386,6 @@ // ---------- Start ---------- initProgressListener(); checkTools(); + loadSettings(); checkForUpdate(true); })(); diff --git a/app/index.html b/app/index.html index 1659abc..3455ac8 100644 --- a/app/index.html +++ b/app/index.html @@ -81,6 +81,18 @@
+ +
+ + +
+
diff --git a/tauri-app/package.json b/tauri-app/package.json index c1392c7..820c95c 100644 --- a/tauri-app/package.json +++ b/tauri-app/package.json @@ -1,6 +1,6 @@ { "name": "novaphonic", - "version": "0.1.2", + "version": "0.1.3", "private": true, "scripts": { "tauri": "tauri" diff --git a/tauri-app/src-tauri/Cargo.toml b/tauri-app/src-tauri/Cargo.toml index cffecad..fcc6f35 100644 --- a/tauri-app/src-tauri/Cargo.toml +++ b/tauri-app/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "novaphonic" -version = "0.1.2" +version = "0.1.3" edition = "2021" description = "NovaPhonic, ein Tool von KI-WERKE zum automatischen Videoschnitt und zur Audio Veredelung" diff --git a/tauri-app/src-tauri/src/main.rs b/tauri-app/src-tauri/src/main.rs index 2246710..25ff4ac 100644 --- a/tauri-app/src-tauri/src/main.rs +++ b/tauri-app/src-tauri/src/main.rs @@ -20,7 +20,7 @@ use serde::{Deserialize, Serialize}; use std::fs; use std::path::{Path, PathBuf}; -use tauri::{AppHandle, Emitter}; +use tauri::{AppHandle, Emitter, Manager}; use tauri_plugin_shell::process::CommandEvent; use tauri_plugin_shell::ShellExt; @@ -75,6 +75,93 @@ fn cleanup_temp_work_dir(source_path: &str) { } } +// ---------- Einstellungen (automatisches Standardspeicherziel) ---------- +// Wird als kleine JSON Datei im App Konfigurationsordner des Nutzers abgelegt +// (z.B. unter Windows in AppData\Roaming\...), nicht im Temp-Ordner, damit sie +// App-Neustarts übersteht. Betrifft ausschließlich diese Einstellung, niemals +// Videoinhalte oder Zwischenergebnisse. +#[derive(Serialize, Deserialize, Clone, Default)] +#[serde(rename_all = "camelCase")] +struct AppSettings { + auto_save_enabled: bool, + auto_save_folder: Option, +} + +fn settings_path(app: &AppHandle) -> Result { + let dir = app + .path() + .app_config_dir() + .map_err(|e| format!("Konnte Konfigurationsordner nicht ermitteln: {e}"))?; + fs::create_dir_all(&dir).map_err(|e| e.to_string())?; + Ok(dir.join("settings.json")) +} + +#[tauri::command] +fn load_settings(app: AppHandle) -> AppSettings { + let Ok(path) = settings_path(&app) else { + return AppSettings::default(); + }; + match fs::read_to_string(&path) { + Ok(text) => serde_json::from_str(&text).unwrap_or_default(), + Err(_) => AppSettings::default(), + } +} + +#[tauri::command] +fn save_settings(app: AppHandle, settings: AppSettings) -> Result<(), String> { + let path = settings_path(&app)?; + let text = serde_json::to_string_pretty(&settings).map_err(|e| e.to_string())?; + fs::write(&path, text).map_err(|e| format!("Konnte Einstellungen nicht speichern: {e}")) +} + +#[tauri::command] +fn pick_save_folder() -> Option { + let folder = rfd::FileDialog::new().pick_folder()?; + Some(folder.to_string_lossy().to_string()) +} + +// Kopiert das fertige Ergebnis direkt in den vom Nutzer festgelegten Standard- +// Ordner, ohne "Speichern unter" Dialog, und räumt danach wie gewohnt den +// temporären Arbeitsordner auf. Bei einem Namenskonflikt wird die bestehende +// Datei bewusst nicht überschrieben, sondern ein Zähler an den Dateinamen +// angehängt, damit nie versehentlich ein älteres Ergebnis verloren geht. +#[tauri::command] +fn auto_save_output( + source_path: String, + suggested_name: String, + target_folder: String, +) -> Result { + let folder = PathBuf::from(&target_folder); + if !folder.is_dir() { + return Err(format!( + "Zielordner nicht gefunden: {}", + folder.display() + )); + } + let name_path = PathBuf::from(&suggested_name); + let stem = name_path + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("nova_ergebnis") + .to_string(); + let ext = name_path + .extension() + .and_then(|s| s.to_str()) + .unwrap_or("mp4") + .to_string(); + + let mut dest = folder.join(&suggested_name); + let mut counter = 1; + while dest.exists() { + dest = folder.join(format!("{stem} ({counter}).{ext}")); + counter += 1; + } + + fs::copy(&source_path, &dest).map_err(|e| format!("Konnte Datei nicht speichern: {e}"))?; + cleanup_temp_work_dir(&source_path); + Ok(dest.to_string_lossy().to_string()) +} + // Wichtig: Tauri spricht Sidecar Programme zur Laufzeit nur über ihren bloßen // Namen an (z.B. "auto-editor"), der Ordnerpfad "binaries/" wird ausschließlich // beim Einrichten in tauri.conf.json (externalBin) und in den Capabilities @@ -485,7 +572,11 @@ fn main() { pick_video_file, save_output_as, check_tools, - process_video + process_video, + load_settings, + save_settings, + pick_save_folder, + auto_save_output ]) .run(tauri::generate_context!()) .expect("Fehler beim Starten der Tauri Anwendung"); diff --git a/tauri-app/src-tauri/tauri.conf.json b/tauri-app/src-tauri/tauri.conf.json index 54d92e7..6a95c13 100644 --- a/tauri-app/src-tauri/tauri.conf.json +++ b/tauri-app/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "NovaPhonic", - "version": "0.1.2", + "version": "0.1.3", "identifier": "com.kiwerkepro.novaphonic", "build": { "frontendDist": "../../app"