import Cocoa import ZIPFoundation import AVFoundation @NSApplicationMain class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations { var exporter: AVAssetExportSession? @IBAction func showInfoAction(_ sender: Any) { NSApplication.shared.keyWindow?.contentViewController?.performSegue(withIdentifier: "showInfo", sender: self) } @IBAction func showTimelapseAction(_ sender: Any) { NSApplication.shared.keyWindow?.contentViewController?.performSegue(withIdentifier: "showTimelapse", sender: self) } func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool { // Show timelapse and show info buttons if(item.tag == 67 || item.tag == 68) { return NSApplication.shared.keyWindow != nil } return true } @IBAction func exportAction(_ sender: Any) { let document = NSApplication.shared.keyWindow?.windowController?.document as? Document; let savePanel = NSSavePanel() savePanel.title = "Save" savePanel.allowedFileTypes = ["public.png", "public.jpeg"] savePanel.accessoryView = ExportAccessoryView.fromNib() savePanel.begin { (result) in if result.rawValue == NSApplication.ModalResponse.OK.rawValue { let canvas = document?.makeComposite() let canvasTiff = canvas?.tiffRepresentation let bitmapImage = NSBitmapImageRep(data: canvasTiff!) let canvasPng = bitmapImage!.representation(using: .png, properties: [:]) try? canvasPng?.write(to: savePanel.url!) } } } @IBAction func exportThumbnailAction(_ sender: Any) { let document = NSApplication.shared.keyWindow?.windowController?.document as? Document; let savePanel = NSSavePanel() savePanel.title = "Save Thumbnail" savePanel.allowedFileTypes = ["public.png"] savePanel.begin { (result) in if result.rawValue == NSApplication.ModalResponse.OK.rawValue { let canvas = document?.makeThumbnail() let canvasTiff = canvas?.tiffRepresentation let bitmapImage = NSBitmapImageRep(data: canvasTiff!) let canvasPng = bitmapImage!.representation(using: .png, properties: [:]) try? canvasPng?.write(to: savePanel.url!) } } } @IBAction func exportTimelapseAction(_ sender: Any) { guard let originalWindow = NSApplication.shared.keyWindow else { return } let document = originalWindow.windowController?.document as? Document; let savePanel = NSSavePanel() savePanel.title = "Save Timelapse" savePanel.allowedFileTypes = ["public.mpeg-4"] savePanel.begin { (result) in if result.rawValue == NSApplication.ModalResponse.OK.rawValue { guard let archive = Archive(data: (document?.data)!, accessMode: Archive.AccessMode.read) else { return } let directory = NSTemporaryDirectory() let mixComposition = AVMutableComposition() var instructions: [AVMutableVideoCompositionLayerInstruction] = [] var duration = CMTime.zero for entry in archive.makeIterator() { if entry.path.contains(VideoPath) { let fileName = NSUUID().uuidString + ".mp4" // This returns a URL? even though it is an NSURL class method let fullURL = NSURL.fileURL(withPathComponents: [directory, fileName])! let _ = try? archive.extract(entry, to: fullURL) let asset = AVAsset(url: fullURL) guard let track = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) else { return } try? track.insertTimeRange(CMTimeRangeMake(start: .zero, duration: asset.duration), of: asset.tracks(withMediaType: .video)[0], at: duration) duration = CMTimeAdd(duration, asset.duration) let opacityInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track) opacityInstruction.setOpacity(0.0, at: duration + asset.duration) instructions.append(opacityInstruction) } } let mainInstruction = AVMutableVideoCompositionInstruction() mainInstruction.timeRange = CMTimeRangeMake(start: .zero, duration: duration) mainInstruction.layerInstructions = instructions let mainComposition = AVMutableVideoComposition() mainComposition.instructions = [mainInstruction] mainComposition.frameDuration = CMTimeMake(value: 1, timescale: 30) mainComposition.renderSize = CGSize(width: document!.info.videoFrame.0, height: document!.info.videoFrame.1) self.exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality) self.exporter?.outputURL = savePanel.url! self.exporter?.outputFileType = AVFileType.mp4 self.exporter?.videoComposition = mainComposition let alert = NSAlert() alert.messageText = "Exporting timelapse..." alert.addButton(withTitle: "Cancel") alert.beginSheetModal(for: originalWindow) { (resonse) in self.exporter?.cancelExport() alert.window.close() } self.exporter?.exportAsynchronously { if self.exporter?.status != .cancelled { DispatchQueue.main.sync { alert.window.close() } } } } } } }