 
 enum Quality: String {
 
-  case low
-  case medium
-  case high
-
-  var value: String {
-    switch self {
-    case .low: return AVAssetExportPresetLowQuality
-    case .medium: return AVAssetExportPresetMediumQuality
-    case .high: return AVAssetExportPresetHighestQuality
-    }
-  }
+    case low
+    case medium
+    case high
+
+    var value: String {
+        switch self {
+        case .low: return AVAssetExportPresetLowQuality
+        case .medium: return AVAssetExportPresetMediumQuality
+        case .high: return AVAssetExportPresetHighestQuality
+        }
+    }
 }
 
 fileprivate final class LayerInstruction {
 
-  let instruction: AVMutableVideoCompositionLayerInstruction
-
-  init(track: AVMutableCompositionTrack, transform: CGAffineTransform, duration: CMTime) {
-    instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track)
-    instruction.setTransform(transform, at: kCMTimeZero)
-    instruction.setOpacity(0.0, at: duration)
-  }
+    let instruction: AVMutableVideoCompositionLayerInstruction
+
+    init(track: AVMutableCompositionTrack, transform: CGAffineTransform, duration: CMTime) {
+        instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track)
+        instruction.setTransform(transform, at: kCMTimeZero)
+        instruction.setOpacity(0.0, at: duration)
+    }
 
 }
 
 fileprivate final class Composition {
 
-  let asset = AVMutableComposition()
-  let track: AVMutableCompositionTrack
-
-  init(duration: CMTime, videoAsset: AVAssetTrack) throws {
-    track = asset.addMutableTrack(withMediaType: AVMediaTypeVideo,
-                                  preferredTrackID: Int32(kCMPersistentTrackID_Invalid)
-    )
-    try track.insertTimeRange(CMTimeRangeMake(kCMTimeZero, duration),
-                              of: videoAsset,
-                              at: kCMTimeZero)
-  }
+    let asset = AVMutableComposition()
+    let track: AVMutableCompositionTrack
+
+    init(duration: CMTime, videoAsset: AVAssetTrack) throws {
+        track = asset.addMutableTrack(withMediaType: AVMediaTypeVideo,
+                                      preferredTrackID: Int32(kCMPersistentTrackID_Invalid)
+        )
+        try track.insertTimeRange(CMTimeRangeMake(kCMTimeZero, duration),
+                                  of: videoAsset,
+                                  at: kCMTimeZero)
+    }
 
 }
 
 fileprivate final class Instruction {
 
-  let videoComposition = AVMutableVideoCompositionInstruction()
-
-  init(length: CMTime, layerInstructions: [AVVideoCompositionLayerInstruction]) {
-    videoComposition.timeRange = CMTimeRangeMake(kCMTimeZero, length)
-    videoComposition.layerInstructions = layerInstructions
-  }
+    let videoComposition = AVMutableVideoCompositionInstruction()
+
+    init(length: CMTime, layerInstructions: [AVVideoCompositionLayerInstruction]) {
+        videoComposition.timeRange = CMTimeRangeMake(kCMTimeZero, length)
+        videoComposition.layerInstructions = layerInstructions
+    }
 }
 
 fileprivate final class VideoComposition {
 
-  let composition = AVMutableVideoComposition()
-
-  init(size: CGSize, instruction: Instruction, frameRate: Int32, layer: Layer) {
-    composition.renderSize = size
-    composition.instructions = [instruction.videoComposition]
-    composition.frameDuration = CMTimeMake(1, frameRate)
-    composition.animationTool = AVVideoCompositionCoreAnimationTool(
-      postProcessingAsVideoLayer: layer.videoAndParent.video,
-      in: layer.videoAndParent.parent
-    )
-  }
+    let composition = AVMutableVideoComposition()
+
+    init(size: CGSize, instruction: Instruction, frameRate: Int32, layer: Layer) {
+        composition.renderSize = size
+        composition.instructions = [instruction.videoComposition]
+        composition.frameDuration = CMTimeMake(1, frameRate)
+        composition.animationTool = AVVideoCompositionCoreAnimationTool(
+            postProcessingAsVideoLayer: layer.videoAndParent.video,
+            in: layer.videoAndParent.parent
+        )
+    }
 }
 
 fileprivate final class Layer {
 
-  fileprivate let overlay: UIImage
-  fileprivate let size: CGSize
-  fileprivate let placement: Placement
-
-  init(overlay: UIImage, size: CGSize, placement: Placement) {
-    self.overlay = overlay
-    self.size = size
-    self.placement = placement
-  }
-
-  fileprivate var frame: CGRect {
-    return CGRect(origin: .zero, size: size)
-  }
-
-  fileprivate var overlayFrame: CGRect {
-    return placement.rect(videoSize: size)
-  }
-
-  lazy var videoAndParent: VideoAndParent = {
-    let overlayLayer = CALayer()
-    overlayLayer.contents = self.overlay.cgImage
-    overlayLayer.frame = self.overlayFrame
-    overlayLayer.masksToBounds = true
-
-    let videoLayer = CALayer()
-    videoLayer.frame = self.frame
-
-    let parentLayer = CALayer()
-    parentLayer.frame = self.frame
-    parentLayer.addSublayer(videoLayer)
-    parentLayer.addSublayer(overlayLayer)
-
-    return VideoAndParent(video: videoLayer, parent: parentLayer)
-  }()
-
-  final class VideoAndParent {
-    let video: CALayer
-    let parent: CALayer
-
-    init(video: CALayer, parent: CALayer) {
-      self.video = video
-      self.parent = parent
-    }
-  }
+    fileprivate let overlay: UIImage
+    fileprivate let size: CGSize
+    fileprivate let placement: Placement
+
+    init(overlay: UIImage, size: CGSize, placement: Placement) {
+        self.overlay = overlay
+        self.size = size
+        self.placement = placement
+    }
+
+    fileprivate var frame: CGRect {
+        return CGRect(origin: .zero, size: size)
+    }
+
+    fileprivate var overlayFrame: CGRect {
+        return placement.rect(videoSize: size)
+    }
+
+    lazy var videoAndParent: VideoAndParent = {
+        let overlayLayer = CALayer()
+        overlayLayer.contents = self.overlay.cgImage
+        overlayLayer.frame = self.overlayFrame
+        overlayLayer.masksToBounds = true
+
+        let videoLayer = CALayer()
+        videoLayer.frame = self.frame
+
+        let parentLayer = CALayer()
+        parentLayer.frame = self.frame
+        parentLayer.addSublayer(videoLayer)
+        parentLayer.addSublayer(overlayLayer)
+
+        return VideoAndParent(video: videoLayer, parent: parentLayer)
+    }()
+
+    final class VideoAndParent {
+        let video: CALayer
+        let parent: CALayer
+
+        init(video: CALayer, parent: CALayer) {
+            self.video = video
+            self.parent = parent
+        }
+    }
 }
 
 ///  A wrapper of AVAssetExportSession.
 fileprivate final class Exporter {
 
-  fileprivate let session: AVAssetExportSession
-
-  var progress: ((_ progress: Float) -> Void)?
-
-  init?(asset: AVMutableComposition, outputUrl: URL, composition: AVVideoComposition, quality: Quality) {
-    guard let session = AVAssetExportSession(asset: asset, presetName: quality.value) else { return nil }
-    self.session = session
-    self.session.outputURL = outputUrl
-    self.session.outputFileType = AVFileTypeQuickTimeMovie
-    self.session.videoComposition = composition
-  }
-
-  func render(complete: @escaping (_ url: URL?) -> Void) {
-    let group = DispatchGroup()
-    group.enter()
-    DispatchQueue.global(qos: .utility).async(group: group) {
-      self.session.exportAsynchronously {
-        group.leave()
-        DispatchQueue.main.async {
-          complete(self.session.outputURL)
-        }
-      }
-      self.progress(session: self.session, group: group)
-    }
-  }
-
-  /**
-   Polls the AVAssetExportSession status every 500 milliseconds.
-
-   - Parameter session: AVAssetExportSession
-   - Parameter group: DispatchGroup
-   */
-  private func progress(session: AVAssetExportSession, group: DispatchGroup) {
-    while session.status == .waiting || session.status == .exporting {
-      progress?(session.progress)
-      _ = group.wait(timeout: DispatchTime.now() + .milliseconds(500))
-    }
-
-  }
+    fileprivate let session: AVAssetExportSession
+
+    var progress: ((_ progress: Float) -> Void)?
+
+    init?(asset: AVMutableComposition, outputUrl: URL, composition: AVVideoComposition, quality: Quality) {
+        guard let session = AVAssetExportSession(asset: asset, presetName: quality.value) else { return nil }
+        self.session = session
+        self.session.outputURL = outputUrl
+        self.session.outputFileType = AVFileTypeQuickTimeMovie
+        self.session.videoComposition = composition
+    }
+
+    func render(complete: @escaping (_ url: URL?) -> Void) {
+        let group = DispatchGroup()
+        group.enter()
+        DispatchQueue.global(qos: .utility).async(group: group) {
+            self.session.exportAsynchronously {
+                group.leave()
+                DispatchQueue.main.async {
+                    complete(self.session.outputURL)
+                }
+            }
+            self.progress(session: self.session, group: group)
+        }
+    }
+
+    /**
+     Polls the AVAssetExportSession status every 500 milliseconds.
+
+     - Parameter session: AVAssetExportSession
+     - Parameter group: DispatchGroup
+     */
+    private func progress(session: AVAssetExportSession, group: DispatchGroup) {
+        while session.status == .waiting || session.status == .exporting {
+            progress?(session.progress)
+            _ = group.wait(timeout: DispatchTime.now() + .milliseconds(500))
+        }
+
+    }
 
 }
 /// Provides an easy way to detemine if the video was taken in landscape or portrait.
 private struct Transform {
 
-  fileprivate let transform: CGAffineTransform
-
-  init(_ transform: CGAffineTransform) {
-    self.transform = transform
-  }
-
-  var isPortrait: Bool {
-    guard transform.a == 0 && transform.d == 0 else { return false }
-    switch (transform.b, transform.c) {
-    case(1.0, -1.0): return true
-    case(-1.0, 1.0): return true
-    default: return false
-    }
-  }
+    fileprivate let transform: CGAffineTransform
+
+    init(_ transform: CGAffineTransform) {
+        self.transform = transform
+    }
+
+    var isPortrait: Bool {
+        guard transform.a == 0 && transform.d == 0 else { return false }
+        switch (transform.b, transform.c) {
+        case(1.0, -1.0): return true
+        case(-1.0, 1.0): return true
+        default: return false
+        }
+    }
 }
 
 private struct Size {
-  fileprivate let isPortrait: Bool
-  fileprivate let size: CGSize
-
-  var naturalSize: CGSize {
-    return isPortrait ? CGSize(width: size.height, height: size.width) : size
-  }
+    fileprivate let isPortrait: Bool
+    fileprivate let size: CGSize
+
+    var naturalSize: CGSize {
+        return isPortrait ? CGSize(width: size.height, height: size.width) : size
+    }
 }
 
 /// Configuration struct.  Open for extension.
 struct MergeConfiguration {
-  let frameRate: Int32
-  let directory: String
-  let quality: Quality
-  let placement: Placement
+    let frameRate: Int32
+    let directory: String
+    let quality: Quality
+    let placement: Placement
 
 }
 
 extension MergeConfiguration {
 
-  static var standard: MergeConfiguration {
-    return MergeConfiguration(frameRate: 30, directory: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0], quality: Quality.high, placement: Placement.stretchFit)
-  }
-}
-
+    static var standard: MergeConfiguration {
+        return MergeConfiguration(frameRate: 30, directory: NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0], quality: Quality.high, placement: Placement.stretchFit)
+    }
+}
+

import Foundation
import AVKit
import AVFoundation

final class Merge {

  fileprivate let configuration: MergeConfiguration

  init(config: MergeConfiguration) {
    self.configuration = config
  }

  fileprivate var fileUrl: URL {
    let fullPath = configuration.directory + "/export\(NSUUID().uuidString).mov"
    return URL(fileURLWithPath: fullPath)
  }

  /**
   Overlays and exports a video with a desired UIImage on top.

   - Parameter video: AVAsset
   - Paremeter overlayImage: UIImage
   - Paremeter completion: Completion Handler
   - Parameter progressHandler: Returns the progress every 500 milliseconds.
   */
  func overlayVideo(video: AVAsset,
                    overlayImage: UIImage,
                    completion: @escaping (_ URL: URL?) -> Void,
                    progressHandler: @escaping (_ progress: Float) -> Void) {
    let tracks = video.tracks(withMediaType: AVMediaTypeVideo)
    guard !tracks.isEmpty else { return }
    let track = tracks[0]
    let compositionTry = try? Composition(duration: video.duration, videoAsset: track)

    guard let composition = compositionTry else { return }

    let videoTrack = video.tracks(withMediaType: AVMediaTypeVideo)[0]
    let videoTransform = Transform(videoTrack.preferredTransform)
    let layerInstruction = LayerInstruction(track: composition.track, transform: videoTrack.preferredTransform, duration: video.duration)
    let instruction = Instruction(length: video.duration, layerInstructions: [layerInstruction.instruction])
    let size = Size(isPortrait: videoTransform.isPortrait, size: videoTrack.naturalSize)
    let layer = Layer(overlay: overlayImage, size: size.naturalSize, placement: configuration.placement)
    let videoComposition = VideoComposition(size: size.naturalSize, instruction: instruction,
                                            frameRate: configuration.frameRate,
                                            layer: layer
    )
    Exporter(asset: composition.asset, outputUrl: fileUrl, composition: videoComposition.composition, quality: configuration.quality).map { exporter in
      exporter.render { url in
        completion(url)
      }
      exporter.progress = { progress in
        progressHandler(progress)
      }
      } ?? completion(nil)
  }
}

/**


enum Placement {

    case stretchFit
    case custom(x: CGFloat, y: CGFloat, size: CGSize)

    func rect(videoSize: CGSize) -> CGRect {
        switch self {
        case .stretchFit: return CGRect(origin: .zero, size: videoSize)
        case .custom(let x, let y, let size): return CGRect(x: x, y: y, width: size.width, height: size.height)
        }
    }
}

/**
