diff --git a/kplayer/detail/EditItemView.swift b/kplayer/detail/EditItemView.swift index 016d52e..1ba4335 100644 --- a/kplayer/detail/EditItemView.swift +++ b/kplayer/detail/EditItemView.swift @@ -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( - 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( + Stepper(value: Binding( 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 + } } }