diff --git a/Sources/GrowthLapse/ContentView.swift b/Sources/GrowthLapse/ContentView.swift index 414a517..42ffb9c 100644 --- a/Sources/GrowthLapse/ContentView.swift +++ b/Sources/GrowthLapse/ContentView.swift @@ -35,7 +35,7 @@ struct ContentView: View { Button("Projekt laden") { selectProjectFile() } - .help("Lädt eine gespeicherte .growthlapse-project.json, inklusive Segmentfenstern und Variantenwahl.") + .help("Öffnet ein gespeichertes Projekt mit Ausschnitten, Varianten und Render-Einstellungen. Deine lokalen FFmpeg-Pfade bleiben erhalten.") } .disabled(processor.state.isRunning) @@ -67,6 +67,7 @@ struct ContentView: View { settings.ffprobeExecutable = nil } .disabled(settings.ffmpegExecutable == nil && settings.ffprobeExecutable == nil) + .help("Entfernt die manuell gewählten Werkzeugpfade und sucht FFmpeg und FFprobe automatisch auf diesem Mac.") } .disabled(processor.state.isRunning) @@ -147,7 +148,7 @@ struct ContentView: View { Toggle("Segment aus der Mitte nehmen", isOn: $settings.takeMiddleSegment) .help("Wählt den Ausschnitt aus der Videomitte. Start-Offset verschiebt diesen Mittelpunkt.") Toggle("Zwischenclips behalten", isOn: $settings.keepIntermediateClips) - .help("Behält segment_*, stabilized_*, clip_* und xfade_* Dateien zur Kontrolle. Sonst wird der Temp-Ordner nach Erfolg gelöscht.") + .help("Behält nach erfolgreichem Rendern zusätzliche Zwischenstufen zur Kontrolle. Der Cache für die Nachbearbeitung wird separat eingestellt.") Toggle("Render-Cache für Nachbearbeitung behalten", isOn: $settings.keepRenderCacheForReview) .help("Behält die normalisierten clip_*.mp4 Dateien und eine Projektdatei neben dem Output. So können einzelne Segmentfenster später angepasst und schneller neu gerendert werden.") Toggle("Clipnummer einblenden", isOn: $settings.burnInClipNumbers) @@ -203,6 +204,7 @@ struct ContentView: View { .help("Angestrebte Gesichtshöhe relativ zur Ausgabehöhe. 28% ist ein guter Startwert für Portrait-/GrowthLapse-Material.") Slider(value: $settings.targetFaceHeightRatio, in: 0.18...0.40, step: 0.01) + .help("Gewünschte Gesichtshöhe im Ergebnis: 26 % entspricht etwa einem Viertel der Bildhöhe. Bildränder können die Anpassung begrenzen.") .disabled(!settings.faceNormalizationEnabled) } .disabled(processor.state.isRunning) @@ -219,7 +221,7 @@ struct ContentView: View { Button("STOP") { processor.cancel() } - .help("Bricht den laufenden ffmpeg-Prozess ab und räumt temporäre Dateien auf.") + .help("Stoppt den laufenden Render einschließlich Bildanalyse. Das bisherige Ergebnis und dessen Cache bleiben erhalten.") .tint(.red) .disabled(!processor.state.isRunning) @@ -367,6 +369,7 @@ struct ContentView: View { .font(.caption) .foregroundStyle(.secondary) Button("Ergebnis ansehen") { showingResult = true } + .help("Öffnet das zuletzt gespeicherte Ergebnisvideo in der App. Noch nicht gerenderte Clipänderungen sind darin nicht enthalten.") .disabled(!FileManager.default.fileExists(atPath: project.outputFilePath)) } HStack { @@ -384,7 +387,9 @@ struct ContentView: View { Spacer(minLength: 0) Menu("Projekt") { Button("Projekt speichern") { processor.saveCurrentProject() } + .help("Speichert Ausschnitte und Varianten. Render-Einstellungen werden beim erfolgreichen Rendern ins Projekt übernommen.") Button("Render-Cache löschen") { processor.confirmProjectAndDeleteCache() } + .help("Gibt den Speicherplatz der Zwischenclips frei. Video, Projekt und Quellen bleiben erhalten. Beim nächsten Rendern werden die Zwischenclips neu erstellt.") } .fixedSize() .help("Projekt speichern oder den zugehörigen Cache löschen. Video und Quelldateien bleiben erhalten.") @@ -488,27 +493,34 @@ struct ContentView: View { if maxStart > 0 { Slider(value: startBinding, in: 0...maxStart, step: 0.1) + .help("Verschiebt den Ausschnitt in Schritten von 0,1 Sekunden. Die Änderung wird gespeichert und mit ‚Änderungen neu rendern‘ ins Video übernommen.") } else { Slider(value: .constant(0), in: 0...1) .disabled(true) + .help("Der Ausschnitt umfasst bereits die ganze Quelle und kann nicht verschoben werden.") } HStack { Button("-1 s") { processor.updateClip(clip, segmentStart: startBinding.wrappedValue - 1) } + .help("Verschiebt den Ausschnitt um eine Sekunde nach vorn zum Videoanfang. Die Videogrenzen werden eingehalten.") Button("-0,5 s") { processor.updateClip(clip, segmentStart: startBinding.wrappedValue - 0.5) } + .help("Verschiebt den Ausschnitt um eine halbe Sekunde zum Videoanfang. Die Videogrenzen werden eingehalten.") Button("Zur Mitte") { processor.updateClip(clip, segmentStart: max(0, (liveClip.duration - liveClip.segmentLength) / 2 + settings.startOffset)) } + .help("Setzt den Ausschnitt in die Videomitte und berücksichtigt den eingestellten Start-Offset.") Button("+0,5 s") { processor.updateClip(clip, segmentStart: startBinding.wrappedValue + 0.5) } + .help("Verschiebt den Ausschnitt um eine halbe Sekunde zum Videoende. Die Videogrenzen werden eingehalten.") Button("+1 s") { processor.updateClip(clip, segmentStart: startBinding.wrappedValue + 1) } + .help("Verschiebt den Ausschnitt um eine Sekunde zum Videoende. Die Videogrenzen werden eingehalten.") } .controlSize(.small) .disabled(processor.state.isRunning || maxStart <= 0) @@ -879,6 +891,7 @@ private struct ClipVariantPickerView: View { Button("Schließen") { dismiss() } + .help("Schließt die Variantenwahl, ohne eine andere Variante zu übernehmen.") } if let selectedVariant { @@ -907,6 +920,7 @@ private struct ClipVariantPickerView: View { onSelect(variant) } .disabled(variant.sourcePath == clip.sourcePath) + .help("Übernimmt diese Aufnahme für den Clip. Anschließend neu rendern, damit sie im Ergebnis erscheint.") } .contentShape(Rectangle()) .onTapGesture { @@ -984,6 +998,7 @@ private struct OutputPreviewView: View { Text(url.lastPathComponent).font(.headline) Spacer() Button("Schließen") { dismiss() } + .help("Schließt die Ergebnisvorschau und stoppt ihre Wiedergabe. Auch mit Escape möglich.") .keyboardShortcut(.cancelAction) } VideoPlayer(player: player)