1
Fork 0

Add time-lapse export option

This commit is contained in:
Joshua Goins 2021-09-29 17:27:13 -04:00
parent a801579bc1
commit 7e5028efa7
3 changed files with 137 additions and 2 deletions

View file

@ -1,7 +1,11 @@
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)
}
@ -55,5 +59,94 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations {
}
}
@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);
}
}
}
}
}

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="18122" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="19162" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="18122"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19162"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@ -89,6 +89,11 @@
<action selector="exportThumbnailAction:" target="Voe-Tx-rLC" id="zki-Tz-dom"/>
</connections>
</menuItem>
<menuItem title="Export Timelapse..." keyEquivalent="S" id="mOL-Am-v5d" userLabel="Export Timelapse...">
<connections>
<action selector="exportTimelapseAction:" target="Voe-Tx-rLC" id="U6F-oD-VRr"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="m54-Is-iLE">
<connections>
<action selector="saveDocument:" target="Ady-hI-5gd" id="fGt-aM-WYN"/>

View file

@ -42,6 +42,9 @@ struct SilicaDocument {
var layers: [SilicaLayer] = []
var videoFrameWidth: Int = 0
var videoFrameHeight: Int = 0
lazy var nsSize = {
return NSSize(width: width, height: height)
}()
@ -342,6 +345,19 @@ class Document: NSDocument {
return nil
}
func parsePairString(_ str: String) -> (Int, Int)? {
let sizeComponents = str.replacingOccurrences(of: "{", with: "").replacingOccurrences(of: "}", with: "").components(separatedBy: ", ")
if sizeComponents.count == 2 {
let width = Int(sizeComponents[0])
let height = Int(sizeComponents[1])
return (width!, height!)
} else {
return nil
}
}
func parseSilicaDocument(archive: Archive, dict: NSDictionary) {
let objectsArray = self.dict?["$objects"] as! NSArray
@ -352,6 +368,27 @@ class Document: NSDocument {
info.flippedHorizontally = (dict[FlippedHorizontallyKey] as! NSNumber).boolValue
info.flippedVertically = (dict[FlippedVerticallyKey] as! NSNumber).boolValue
let videoResolutionClassKey = dict["SilicaDocumentVideoSegmentInfoKey"]
let videoResolutionClassID = getClassID(id: videoResolutionClassKey)
let videoResolution = objectsArray[videoResolutionClassID] as! NSDictionary
let frameSizeClassKey = videoResolution["frameSize"]
let frameSizeClassID = getClassID(id: frameSizeClassKey)
let frameSize = objectsArray[frameSizeClassID] as! String
// frameSize
//SilicaDocumentVideoSegmentInfoKey
// videoQualityKey
dump(frameSize, indent: 5)
guard let (frameWidth, frameHeight) = parsePairString(frameSize) else {
return
}
info.videoFrameWidth = frameWidth
info.videoFrameHeight = frameHeight
let colorProfileClassKey = dict["colorProfile"]
let colorProfileClassID = getClassID(id: colorProfileClassKey)
let colorProfile = objectsArray[colorProfileClassID] as! NSDictionary