Review-Ansicht: 9:16-Master mit eingeblendetem 4:5-Rahmen

Schritt 5 der Umsetzungsreihenfolge.

ReviewSheet zeigt die Quelldatei per AVKit VideoPlayer (Scrubbing,
Play/Pause etc. kommen dadurch kostenlos mit) und blendet oben/unten
abgedunkelte Balken plus einen hellen Rahmen für die bleibende 4:5-
Fläche ein. Die Geometrie kommt direkt aus
CropEngine.targetSize(forSourceSize:) — die Vorschau zeigt also exakt
das, was beim tatsächlichen Crop rauskommt, keine zweite Berechnung.

Erreichbar über einen Vorschau-Button pro Zeile in der Dateiliste.

Bewusst noch nicht enthalten (README, "Danach"-Liste): verstellbarer
vertikaler Crop-Offset — die Ansicht ist reine Framing-Kontrolle.
main
Samuel Zielke 1 month ago
parent 64a230a667
commit 4a8583f0f7

@ -2,6 +2,7 @@ import SwiftUI
struct FileListView: View {
@EnvironmentObject private var coordinator: ConversionCoordinator
@State private var previewTarget: PreviewTarget?
var body: some View {
List(coordinator.items) { item in
@ -15,10 +16,26 @@ struct FileListView: View {
.foregroundStyle(item.status.needsAttention ? Color.orange : .secondary)
}
Spacer()
Button {
previewTarget = PreviewTarget(url: item.sourceURL)
} label: {
Image(systemName: "eye")
}
.buttonStyle(.borderless)
.help("Vorschau mit 4:5-Rahmen zur Framing-Kontrolle")
statusIndicator(for: item.status)
}
.padding(.vertical, 2)
}
.sheet(item: $previewTarget) { target in
ReviewSheet(sourceURL: target.url)
}
}
private struct PreviewTarget: Identifiable {
let url: URL
var id: URL { url }
}
@ViewBuilder

@ -0,0 +1,91 @@
import AVKit
import DropTwoCore
import SwiftUI
/// Schritt 5: 9:16-Master mit eingeblendetem 4:5-Rahmen zur Framing-Kontrolle.
/// Scrubbar über die nativen AVKit-Kontrollen von `VideoPlayer`. Der Rahmen
/// nutzt exakt dieselbe Geometrie wie `CropEngine.targetSize` — was hier zu
/// sehen ist, ist tatsächlich das, was beim Crop rauskommt. Rein zur Kontrolle:
/// der Offset ist hier noch nicht verstellbar (README, "Danach"-Liste).
struct ReviewSheet: View {
let sourceURL: URL
@Environment(\.dismiss) private var dismiss
@State private var player: AVPlayer?
@State private var videoSize: CGSize?
@State private var loadError: String?
var body: some View {
VStack(spacing: 12) {
HStack {
Text(sourceURL.lastPathComponent)
.font(.headline)
.lineLimit(1)
.truncationMode(.middle)
Spacer()
Button("Schließen") { dismiss() }
}
content
}
.padding(16)
.frame(minWidth: 420, minHeight: 520)
.task { await load() }
}
@ViewBuilder
private var content: some View {
if let loadError {
VStack(spacing: 8) {
Image(systemName: "exclamationmark.triangle")
.font(.title)
Text(loadError)
}
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else if let player, let videoSize, videoSize.height > 0 {
GeometryReader { proxy in
ZStack {
VideoPlayer(player: player)
cropOverlay(sourceSize: videoSize, viewSize: proxy.size)
}
}
.aspectRatio(videoSize.width / videoSize.height, contentMode: .fit)
} else {
ProgressView("Lädt Vorschau …")
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
/// Zwei abgedunkelte Balken oben/unten markieren, was beim Crop wegfällt;
/// der helle Rahmen zeigt die bleibende 4:5-Fläche — volle Breite,
/// vertikal zentriert, exakt wie `CropEngine` es tatsächlich schneidet.
@ViewBuilder
private func cropOverlay(sourceSize: CGSize, viewSize: CGSize) -> some View {
let cropSize = CropEngine.targetSize(forSourceSize: sourceSize)
let marginFraction = max(0, (1 - cropSize.height / sourceSize.height) / 2)
let marginHeight = viewSize.height * marginFraction
VStack(spacing: 0) {
Rectangle()
.fill(Color.black.opacity(0.55))
.frame(height: marginHeight)
Rectangle()
.strokeBorder(Color(red: 0.784, green: 0.663, blue: 0.416), lineWidth: 2)
.frame(height: max(viewSize.height - marginHeight * 2, 0))
Rectangle()
.fill(Color.black.opacity(0.55))
.frame(height: marginHeight)
}
.allowsHitTesting(false)
}
private func load() async {
do {
let info = try await VideoInspector.inspect(sourceURL)
videoSize = info.pixelSize
player = AVPlayer(url: sourceURL)
} catch {
loadError = "Konnte Vorschau nicht laden."
}
}
}
Loading…
Cancel
Save

Powered by TurnKey Linux.