2020-03-10 14:23:54 -04:00
|
|
|
import Cocoa
|
2021-09-29 17:27:13 -04:00
|
|
|
import ZIPFoundation
|
|
|
|
import AVFoundation
|
2020-03-10 14:23:54 -04:00
|
|
|
|
|
|
|
@NSApplicationMain
|
2020-03-10 16:23:25 -04:00
|
|
|
class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations {
|
2021-09-29 17:27:13 -04:00
|
|
|
var exporter: AVAssetExportSession?
|
|
|
|
|
2020-03-10 16:23:25 -04:00
|
|
|
@IBAction func showInfoAction(_ sender: Any) {
|
|
|
|
NSApplication.shared.keyWindow?.contentViewController?.performSegue(withIdentifier: "showInfo", sender: self)
|
|
|
|
}
|
|
|
|
|
2021-05-09 21:44:04 -04:00
|
|
|
@IBAction func showTimelapseAction(_ sender: Any) {
|
|
|
|
NSApplication.shared.keyWindow?.contentViewController?.performSegue(withIdentifier: "showTimelapse", sender: self)
|
|
|
|
}
|
|
|
|
|
2020-03-10 16:23:25 -04:00
|
|
|
func validateUserInterfaceItem(_ item: NSValidatedUserInterfaceItem) -> Bool {
|
2021-05-09 21:44:04 -04:00
|
|
|
// Show timelapse and show info buttons
|
|
|
|
if(item.tag == 67 || item.tag == 68) {
|
2020-03-11 22:15:05 -04:00
|
|
|
return NSApplication.shared.keyWindow != nil
|
2020-03-10 16:23:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
2021-09-16 19:03:32 -04:00
|
|
|
|
|
|
|
@IBAction func exportAction(_ sender: Any) {
|
|
|
|
let document = NSApplication.shared.keyWindow?.windowController?.document as? Document;
|
|
|
|
|
|
|
|
let savePanel = NSSavePanel()
|
|
|
|
savePanel.title = "Save"
|
|
|
|
savePanel.allowedFileTypes = ["public.png"]
|
|
|
|
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!)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-29 17:27:13 -04:00
|
|
|
@IBAction func exportTimelapseAction(_ sender: Any) {
|
|
|
|
let document = NSApplication.shared.keyWindow?.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 duration = CMTime.zero
|
|
|
|
|
|
|
|
var instructions: [AVMutableVideoCompositionLayerInstruction] = []
|
|
|
|
|
|
|
|
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 firstTrack = mixComposition.addMutableTrack(
|
|
|
|
withMediaType: .video,
|
|
|
|
preferredTrackID: Int32(kCMPersistentTrackID_Invalid))
|
|
|
|
else { return }
|
|
|
|
|
|
|
|
// 3
|
|
|
|
do {
|
|
|
|
try firstTrack.insertTimeRange(
|
|
|
|
CMTimeRangeMake(start: .zero, duration: asset.duration),
|
|
|
|
of: asset.tracks(withMediaType: .video)[0],
|
|
|
|
at: duration)
|
|
|
|
} catch {
|
|
|
|
print("Failed to load first track")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
duration = CMTimeAdd(duration, asset.duration)
|
|
|
|
|
|
|
|
let firstInstruction = AVMutableVideoCompositionLayerInstruction(
|
|
|
|
assetTrack: firstTrack)
|
|
|
|
firstInstruction.setOpacity(0.0, at: duration + asset.duration)
|
|
|
|
|
|
|
|
instructions.append(firstInstruction)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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.videoFrameWidth,
|
|
|
|
height: document!.info.videoFrameHeight)
|
|
|
|
|
|
|
|
self.exporter = AVAssetExportSession(
|
|
|
|
asset: mixComposition,
|
|
|
|
presetName: AVAssetExportPresetHighestQuality)
|
|
|
|
|
|
|
|
self.exporter?.outputURL = savePanel.url!
|
|
|
|
self.exporter?.outputFileType = AVFileType.mp4
|
|
|
|
self.exporter?.shouldOptimizeForNetworkUse = true
|
|
|
|
self.exporter?.videoComposition = mainComposition
|
|
|
|
|
|
|
|
self.exporter?.exportAsynchronously {
|
|
|
|
|
|
|
|
dump(self.exporter?.error?.localizedDescription);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-10 14:23:54 -04:00
|
|
|
}
|
|
|
|
|