Add contextual tooltips for project editing and result preview
This commit is contained in:
@@ -35,7 +35,7 @@ struct ContentView: View {
|
|||||||
Button("Projekt laden") {
|
Button("Projekt laden") {
|
||||||
selectProjectFile()
|
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)
|
.disabled(processor.state.isRunning)
|
||||||
|
|
||||||
@@ -67,6 +67,7 @@ struct ContentView: View {
|
|||||||
settings.ffprobeExecutable = nil
|
settings.ffprobeExecutable = nil
|
||||||
}
|
}
|
||||||
.disabled(settings.ffmpegExecutable == nil && 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)
|
.disabled(processor.state.isRunning)
|
||||||
|
|
||||||
@@ -147,7 +148,7 @@ struct ContentView: View {
|
|||||||
Toggle("Segment aus der Mitte nehmen", isOn: $settings.takeMiddleSegment)
|
Toggle("Segment aus der Mitte nehmen", isOn: $settings.takeMiddleSegment)
|
||||||
.help("Wählt den Ausschnitt aus der Videomitte. Start-Offset verschiebt diesen Mittelpunkt.")
|
.help("Wählt den Ausschnitt aus der Videomitte. Start-Offset verschiebt diesen Mittelpunkt.")
|
||||||
Toggle("Zwischenclips behalten", isOn: $settings.keepIntermediateClips)
|
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)
|
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.")
|
.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)
|
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.")
|
.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)
|
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(!settings.faceNormalizationEnabled)
|
||||||
}
|
}
|
||||||
.disabled(processor.state.isRunning)
|
.disabled(processor.state.isRunning)
|
||||||
@@ -219,7 +221,7 @@ struct ContentView: View {
|
|||||||
Button("STOP") {
|
Button("STOP") {
|
||||||
processor.cancel()
|
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)
|
.tint(.red)
|
||||||
.disabled(!processor.state.isRunning)
|
.disabled(!processor.state.isRunning)
|
||||||
|
|
||||||
@@ -367,6 +369,7 @@ struct ContentView: View {
|
|||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundStyle(.secondary)
|
||||||
Button("Ergebnis ansehen") { showingResult = true }
|
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))
|
.disabled(!FileManager.default.fileExists(atPath: project.outputFilePath))
|
||||||
}
|
}
|
||||||
HStack {
|
HStack {
|
||||||
@@ -384,7 +387,9 @@ struct ContentView: View {
|
|||||||
Spacer(minLength: 0)
|
Spacer(minLength: 0)
|
||||||
Menu("Projekt") {
|
Menu("Projekt") {
|
||||||
Button("Projekt speichern") { processor.saveCurrentProject() }
|
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() }
|
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()
|
.fixedSize()
|
||||||
.help("Projekt speichern oder den zugehörigen Cache löschen. Video und Quelldateien bleiben erhalten.")
|
.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 {
|
if maxStart > 0 {
|
||||||
Slider(value: startBinding, in: 0...maxStart, step: 0.1)
|
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 {
|
} else {
|
||||||
Slider(value: .constant(0), in: 0...1)
|
Slider(value: .constant(0), in: 0...1)
|
||||||
.disabled(true)
|
.disabled(true)
|
||||||
|
.help("Der Ausschnitt umfasst bereits die ganze Quelle und kann nicht verschoben werden.")
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Button("-1 s") {
|
Button("-1 s") {
|
||||||
processor.updateClip(clip, segmentStart: startBinding.wrappedValue - 1)
|
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") {
|
Button("-0,5 s") {
|
||||||
processor.updateClip(clip, segmentStart: startBinding.wrappedValue - 0.5)
|
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") {
|
Button("Zur Mitte") {
|
||||||
processor.updateClip(clip, segmentStart: max(0, (liveClip.duration - liveClip.segmentLength) / 2 + settings.startOffset))
|
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") {
|
Button("+0,5 s") {
|
||||||
processor.updateClip(clip, segmentStart: startBinding.wrappedValue + 0.5)
|
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") {
|
Button("+1 s") {
|
||||||
processor.updateClip(clip, segmentStart: startBinding.wrappedValue + 1)
|
processor.updateClip(clip, segmentStart: startBinding.wrappedValue + 1)
|
||||||
}
|
}
|
||||||
|
.help("Verschiebt den Ausschnitt um eine Sekunde zum Videoende. Die Videogrenzen werden eingehalten.")
|
||||||
}
|
}
|
||||||
.controlSize(.small)
|
.controlSize(.small)
|
||||||
.disabled(processor.state.isRunning || maxStart <= 0)
|
.disabled(processor.state.isRunning || maxStart <= 0)
|
||||||
@@ -879,6 +891,7 @@ private struct ClipVariantPickerView: View {
|
|||||||
Button("Schließen") {
|
Button("Schließen") {
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
.help("Schließt die Variantenwahl, ohne eine andere Variante zu übernehmen.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if let selectedVariant {
|
if let selectedVariant {
|
||||||
@@ -907,6 +920,7 @@ private struct ClipVariantPickerView: View {
|
|||||||
onSelect(variant)
|
onSelect(variant)
|
||||||
}
|
}
|
||||||
.disabled(variant.sourcePath == clip.sourcePath)
|
.disabled(variant.sourcePath == clip.sourcePath)
|
||||||
|
.help("Übernimmt diese Aufnahme für den Clip. Anschließend neu rendern, damit sie im Ergebnis erscheint.")
|
||||||
}
|
}
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
@@ -984,6 +998,7 @@ private struct OutputPreviewView: View {
|
|||||||
Text(url.lastPathComponent).font(.headline)
|
Text(url.lastPathComponent).font(.headline)
|
||||||
Spacer()
|
Spacer()
|
||||||
Button("Schließen") { dismiss() }
|
Button("Schließen") { dismiss() }
|
||||||
|
.help("Schließt die Ergebnisvorschau und stoppt ihre Wiedergabe. Auch mit Escape möglich.")
|
||||||
.keyboardShortcut(.cancelAction)
|
.keyboardShortcut(.cancelAction)
|
||||||
}
|
}
|
||||||
VideoPlayer(player: player)
|
VideoPlayer(player: player)
|
||||||
|
|||||||
Reference in New Issue
Block a user