|
|
|
@ -12,7 +12,7 @@ protocol EditItemDelegate { |
|
|
|
func setEnd() |
|
|
|
func cancelEdit() |
|
|
|
func okEdit() |
|
|
|
func seek(_ : Double) |
|
|
|
func seek(_: Double) |
|
|
|
} |
|
|
|
|
|
|
|
struct TagEditor: View { |
|
|
|
@ -36,8 +36,7 @@ struct TagEditor: View { |
|
|
|
Button(action: { |
|
|
|
if item.tags.contains(tag) { |
|
|
|
item.tags.removeAll(where: { toRemove in toRemove == tag }) |
|
|
|
} |
|
|
|
else { |
|
|
|
} else { |
|
|
|
item.tags.append(tag) |
|
|
|
} |
|
|
|
if let c = self.completionHandler { |
|
|
|
@ -49,12 +48,13 @@ struct TagEditor: View { |
|
|
|
.padding(8) |
|
|
|
.background( |
|
|
|
RoundedRectangle(cornerRadius: 8) |
|
|
|
.fill( item.tags.contains(tag) ? |
|
|
|
.fill(item.tags.contains(tag) ? |
|
|
|
Color.yellow.opacity(0.4) : |
|
|
|
Color.gray.opacity(0.4) |
|
|
|
) |
|
|
|
) |
|
|
|
}).buttonStyle(BorderlessButtonStyle()) |
|
|
|
}) |
|
|
|
.buttonStyle(BorderlessButtonStyle()) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -69,6 +69,33 @@ struct EditItemView: View { |
|
|
|
@State |
|
|
|
var snap = true |
|
|
|
|
|
|
|
@State |
|
|
|
var processedImageURL: String? |
|
|
|
|
|
|
|
@State |
|
|
|
var isProcessing = false |
|
|
|
|
|
|
|
@State |
|
|
|
var showProcessedImage = false |
|
|
|
|
|
|
|
@State |
|
|
|
var loadedImage: UIImage? |
|
|
|
|
|
|
|
@State |
|
|
|
var imageLoadError: String? |
|
|
|
|
|
|
|
@State |
|
|
|
var imageScale: CGFloat = 1.0 |
|
|
|
|
|
|
|
@State |
|
|
|
var imageOffset: CGSize = .zero |
|
|
|
|
|
|
|
@State |
|
|
|
var lastScale: CGFloat = 1.0 |
|
|
|
|
|
|
|
@State |
|
|
|
var lastOffset: CGSize = .zero |
|
|
|
|
|
|
|
var len: Double |
|
|
|
|
|
|
|
var delegate: EditItemDelegate |
|
|
|
@ -83,7 +110,7 @@ struct EditItemView: View { |
|
|
|
set: { |
|
|
|
scrubber = $0 |
|
|
|
delegate.seek($0) |
|
|
|
print ($0) |
|
|
|
print($0) |
|
|
|
} |
|
|
|
), in: item.time...(item.time + item.length)) |
|
|
|
|
|
|
|
@ -91,69 +118,78 @@ struct EditItemView: View { |
|
|
|
Text("Start \(EditItemView.formatSecondsToString(item.time)) End \(EditItemView.formatSecondsToString(item.time + item.length)) Length \(item.length, specifier: "%.1f")") |
|
|
|
HStack { |
|
|
|
Stepper(value: Binding<Double>( |
|
|
|
get: { item.time }, |
|
|
|
set: { s in |
|
|
|
item.time = s |
|
|
|
delegate.seek(item.time) |
|
|
|
}), in: 0...len){ |
|
|
|
get: { item.time }, |
|
|
|
set: { s in |
|
|
|
item.time = s |
|
|
|
delegate.seek(item.time) |
|
|
|
}), in: 0...len) { |
|
|
|
Button(action: delegate.setStart, label: { |
|
|
|
Text("Start") |
|
|
|
}).buttonStyle(BorderlessButtonStyle()); |
|
|
|
}) |
|
|
|
.buttonStyle(BorderlessButtonStyle()); |
|
|
|
} |
|
|
|
Stepper(value:Binding<Double>( |
|
|
|
Stepper(value: Binding<Double>( |
|
|
|
get: { item.length }, |
|
|
|
set: { l in |
|
|
|
item.length = l |
|
|
|
delegate.seek(item.time + item.length) |
|
|
|
} |
|
|
|
), in: 1...500){ |
|
|
|
), in: 1...500) { |
|
|
|
Text("Len") |
|
|
|
} |
|
|
|
Button(action: delegate.setEnd, label: { |
|
|
|
Text("End") |
|
|
|
}).buttonStyle(BorderlessButtonStyle()); |
|
|
|
}) |
|
|
|
.buttonStyle(BorderlessButtonStyle()); |
|
|
|
} |
|
|
|
Text("Zoom \(item.scale, specifier: "%.1f") X \(item.offset.x, specifier: "%.1f") Y \(item.offset.y, specifier: "%.1f") ") |
|
|
|
HStack { |
|
|
|
Button(action: delegate.captureZoom, label: { |
|
|
|
Text("Zoom") |
|
|
|
}).padding(5).buttonStyle(BorderlessButtonStyle()); |
|
|
|
}) |
|
|
|
.padding(5).buttonStyle(BorderlessButtonStyle()); |
|
|
|
Button(action: { |
|
|
|
item.scale = 1.0 |
|
|
|
item.offset = CGPoint(x: 0,y: 0) |
|
|
|
item.offset = CGPoint(x: 0, y: 0) |
|
|
|
item.objectWillChange.send() |
|
|
|
}, label: { |
|
|
|
Text("Reset") |
|
|
|
}).padding(5).buttonStyle(BorderlessButtonStyle()); |
|
|
|
Stepper(value:$item.rating, in: -1...5){ |
|
|
|
}) |
|
|
|
.padding(5).buttonStyle(BorderlessButtonStyle()); |
|
|
|
Stepper(value: $item.rating, in: -1...5) { |
|
|
|
Text("*\(item.rating)").frame(width: 25) |
|
|
|
} |
|
|
|
Button(action: { |
|
|
|
delegate.okEdit() |
|
|
|
}, label: { |
|
|
|
Text("ok") |
|
|
|
}).padding(5).buttonStyle(BorderlessButtonStyle());Button(action: { |
|
|
|
}) |
|
|
|
.padding(5).buttonStyle(BorderlessButtonStyle()); Button(action: { |
|
|
|
delegate.cancelEdit() |
|
|
|
}, label: { |
|
|
|
Text("cancel") |
|
|
|
}).padding(5).buttonStyle(BorderlessButtonStyle()); |
|
|
|
}) |
|
|
|
.padding(5).buttonStyle(BorderlessButtonStyle()); |
|
|
|
} |
|
|
|
VStack { |
|
|
|
KToggleButton(text: "snap", binding: $snap) |
|
|
|
Button(action: {faceSelectedItem("sarah"); }, label: {Text("sarah")}).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: {faceSelectedItem("claudia"); }, label: {Text("claudia")}).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: {faceSelectedItem("jessica"); }, label: {Text("jessica")}).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: {faceSelectedItem("marleen"); }, label: {Text("marleen")}).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: {faceSelectedItem("renate"); }, label: {Text("renate")}).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: {faceSelectedItem("birgit"); }, label: {Text("birgit")}).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: {faceSelectedItem("barbara"); }, label: {Text("barbara")}).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: {faceSelectedItem("nina"); }, label: {Text("nina")}).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: {faceSelectedItem("amruta"); }, label: {Text("amruta")}).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: { faceSelectedItem("sarah"); }, label: { Text("sarah") }).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: { faceSelectedItem("claudia"); }, label: { Text("claudia") }).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: { faceSelectedItem("jessica"); }, label: { Text("jessica") }).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: { faceSelectedItem("marleen"); }, label: { Text("marleen") }).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: { faceSelectedItem("renate"); }, label: { Text("renate") }).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: { faceSelectedItem("birgit"); }, label: { Text("birgit") }).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: { faceSelectedItem("barbara"); }, label: { Text("barbara") }).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: { faceSelectedItem("nina"); }, label: { Text("nina") }).buttonStyle(BorderlessButtonStyle()) |
|
|
|
Button(action: { faceSelectedItem("amruta"); }, label: { Text("amruta") }).buttonStyle(BorderlessButtonStyle()) |
|
|
|
} |
|
|
|
|
|
|
|
TagEditor(item: item) |
|
|
|
|
|
|
|
}.background(Color.clear) |
|
|
|
}.background(Color.clear) |
|
|
|
} |
|
|
|
.background(Color.clear) |
|
|
|
} |
|
|
|
.background(Color.clear) |
|
|
|
} |
|
|
|
.onAppear { |
|
|
|
UITableView.appearance().backgroundColor = .clear |
|
|
|
@ -162,36 +198,185 @@ struct EditItemView: View { |
|
|
|
UITableView.appearance().backgroundColor = .systemBackground |
|
|
|
} |
|
|
|
.frame(height: 800, alignment: .top) |
|
|
|
.sheet(isPresented: $showProcessedImage) { |
|
|
|
NavigationView { |
|
|
|
VStack { |
|
|
|
if let image = loadedImage { |
|
|
|
GeometryReader { geometry in |
|
|
|
Image(uiImage: image) |
|
|
|
.resizable() |
|
|
|
.aspectRatio(contentMode: .fit) |
|
|
|
.frame(width: geometry.size.width, height: geometry.size.height) |
|
|
|
.scaleEffect(imageScale) |
|
|
|
.offset(imageOffset) |
|
|
|
.gesture( |
|
|
|
SimultaneousGesture( |
|
|
|
MagnificationGesture() |
|
|
|
.onChanged { value in |
|
|
|
let delta = value / lastScale |
|
|
|
lastScale = value |
|
|
|
let newScale = imageScale * delta |
|
|
|
imageScale = min(max(newScale, 1.0), 5.0) |
|
|
|
} |
|
|
|
.onEnded { value in |
|
|
|
lastScale = 1.0 |
|
|
|
}, |
|
|
|
DragGesture(minimumDistance: 0) |
|
|
|
.onChanged { value in |
|
|
|
imageOffset = CGSize( |
|
|
|
width: lastOffset.width + value.translation.width, |
|
|
|
height: lastOffset.height + value.translation.height |
|
|
|
) |
|
|
|
} |
|
|
|
.onEnded { value in |
|
|
|
lastOffset = imageOffset |
|
|
|
} |
|
|
|
) |
|
|
|
) |
|
|
|
.onTapGesture(count: 2) { |
|
|
|
// Double tap to reset zoom |
|
|
|
withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { |
|
|
|
imageScale = 1.0 |
|
|
|
lastScale = 1.0 |
|
|
|
imageOffset = .zero |
|
|
|
lastOffset = .zero |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} else if let error = imageLoadError { |
|
|
|
VStack(spacing: 20) { |
|
|
|
Image(systemName: "exclamationmark.triangle") |
|
|
|
.font(.system(size: 60)) |
|
|
|
.foregroundColor(.orange) |
|
|
|
Text("Failed to load image") |
|
|
|
.font(.headline) |
|
|
|
Text(error) |
|
|
|
.font(.caption) |
|
|
|
.foregroundColor(.secondary) |
|
|
|
if let imageURL = processedImageURL { |
|
|
|
Text("URL: \(imageURL)") |
|
|
|
.font(.caption2) |
|
|
|
.foregroundColor(.secondary) |
|
|
|
} |
|
|
|
} |
|
|
|
.padding() |
|
|
|
} else { |
|
|
|
ProgressView("Loading image...") |
|
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity) |
|
|
|
} |
|
|
|
} |
|
|
|
.navigationTitle("Processed Image") |
|
|
|
.navigationBarTitleDisplayMode(.inline) |
|
|
|
.toolbar { |
|
|
|
ToolbarItem(placement: .navigationBarLeading) { |
|
|
|
if loadedImage != nil && imageScale != 1.0 { |
|
|
|
Button("Reset") { |
|
|
|
withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { |
|
|
|
imageScale = 1.0 |
|
|
|
lastScale = 1.0 |
|
|
|
imageOffset = .zero |
|
|
|
lastOffset = .zero |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
ToolbarItem(placement: .navigationBarTrailing) { |
|
|
|
Button("Done") { |
|
|
|
showProcessedImage = false |
|
|
|
loadedImage = nil |
|
|
|
imageLoadError = nil |
|
|
|
// Reset zoom state |
|
|
|
imageScale = 1.0 |
|
|
|
lastScale = 1.0 |
|
|
|
imageOffset = .zero |
|
|
|
lastOffset = .zero |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
.task { |
|
|
|
if let imageURL = processedImageURL { |
|
|
|
await loadImageFromURL(imageURL) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
//Spacer() |
|
|
|
} |
|
|
|
|
|
|
|
func loadImageFromURL(_ urlString: String) async { |
|
|
|
guard let url = URL(string: urlString) else { |
|
|
|
imageLoadError = "Invalid URL" |
|
|
|
return |
|
|
|
} |
|
|
|
|
|
|
|
do { |
|
|
|
let (data, _) = try await URLSession.shared.data(from: url) |
|
|
|
if let image = UIImage(data: data) { |
|
|
|
await MainActor.run { |
|
|
|
loadedImage = image |
|
|
|
imageLoadError = nil |
|
|
|
} |
|
|
|
} else { |
|
|
|
await MainActor.run { |
|
|
|
imageLoadError = "Failed to create image from data" |
|
|
|
} |
|
|
|
} |
|
|
|
} catch { |
|
|
|
await MainActor.run { |
|
|
|
imageLoadError = "Failed to load image: \(error.localizedDescription)" |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func faceSelectedItem(_ name: String) { |
|
|
|
let path = item.fullPath.replacing("/srv/samba/ren", with: "z:") |
|
|
|
let outpath1 = path.replacing("/", with: "") |
|
|
|
let outpath = outpath1.replacing("z:", with: "z:/cut/videos/"+name+"/") |
|
|
|
let outpath = outpath1.replacing("z:", with: "z:/cut/videos/" + name + "/") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print(path) |
|
|
|
print(outpath1) |
|
|
|
print(outpath) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Task { |
|
|
|
isProcessing = true |
|
|
|
processedImageURL = nil |
|
|
|
|
|
|
|
if (snap) { |
|
|
|
let imagePath = item.imageUrlAbsolute.replacing("http://linkstation:8089/ren", with: "z:").replacing("_thumb", with: "") |
|
|
|
print(imagePath) |
|
|
|
print(item.thumbUrlAbsolute) |
|
|
|
|
|
|
|
|
|
|
|
let outimagepath = "z:/cut/snapshots/" + name + "/" + imagePath.substringStartingFrom(2).replacing("/", with: "") |
|
|
|
|
|
|
|
try await FaceManager.sharedInstance.processImage(inputImagePath: imagePath, sourceFacePath: "benchmark/"+name+".jpg", outputPath: outimagepath) |
|
|
|
|
|
|
|
do { |
|
|
|
let response = try await FaceManager.sharedInstance.processImage(inputImagePath: imagePath, sourceFacePath: "benchmark/" + name + ".jpg", outputPath: outimagepath) |
|
|
|
|
|
|
|
} |
|
|
|
else { |
|
|
|
try await FaceManager.sharedInstance.processVideo(inputVideoPath: path, sourceFacePath: "benchmark/"+name+".jpg", outputPath: outpath) |
|
|
|
//try! await FaceManager.sharedInstance.processImage(inputImagePath: "input", sourceFacePath: "benchmark/Renate.jpg") |
|
|
|
delegate.cancelEdit() |
|
|
|
//if response.success, let outputPath = response.outputPath { |
|
|
|
// Convert Windows path back to HTTP URL |
|
|
|
let httpURL = outimagepath.replacing("z:", with: "http://linkstation:8089/ren").replacing("\\", with: "/") |
|
|
|
processedImageURL = httpURL |
|
|
|
print("Processed image URL: \(httpURL)") |
|
|
|
|
|
|
|
// Show the sheet with the processed image |
|
|
|
|
|
|
|
|
|
|
|
showProcessedImage = true |
|
|
|
isProcessing = false |
|
|
|
} catch { |
|
|
|
print("Error processing image: \(error)") |
|
|
|
isProcessing = false |
|
|
|
} |
|
|
|
} else { |
|
|
|
do { |
|
|
|
try await FaceManager.sharedInstance.processVideo(inputVideoPath: path, sourceFacePath: "benchmark/" + name + ".jpg", outputPath: outpath) |
|
|
|
delegate.cancelEdit() |
|
|
|
isProcessing = false |
|
|
|
} catch { |
|
|
|
print("Error processing video: \(error)") |
|
|
|
isProcessing = false |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|