Swift：Macのインカメラから取得した映像を鏡写し（左右反転）で保存する（プレビュー付き）

インカメラから映像を取得するのも録画するのも簡単だが，鏡写しで保存するのに意外と手こずったので備忘録．

```swift:CustomView
import Cocoa
import AVFoundation

class CameraView: NSView {
    
    let output = AVCaptureMovieFileOutput() // ファイル出力用
    let session = AVCaptureSession()
    var camera: AVCaptureDevice? = nil
    var previewLayer: AVCaptureVideoPreviewLayer? = nil // プレビュー用

    required init?(coder decoder: NSCoder) {
        super.init(coder: decoder)
        
        wantsLayer = true
        let devices = AVCaptureDevice.devices(for: AVMediaType.video)
        if devices.count > 0 {
            camera = devices.first!
        } else {
            Swift.print("No Available Devices")
            return
        }
        do {
            let input = try AVCaptureDeviceInput(device: camera!)
            // プレビューの設定
            if session.canAddInput(input) {
                session.addInput(input)
                session.sessionPreset = AVCaptureSession.Preset.medium
                previewLayer = AVCaptureVideoPreviewLayer(session: session)
                previewLayer!.frame = bounds
                previewLayer!.videoGravity = AVLayerVideoGravity.resizeAspectFill
                previewLayer!.setAffineTransform(CGAffineTransform(scaleX: -1, y: 1))
                layer?.addSublayer(previewLayer!)
            }
            // ファイル出力の設定
            if session.canAddOutput(output) {
                session.addOutput(output)
                // ここからが鏡写しのキモ
                session.beginConfiguration()
                if let connection = output.connection(with: AVMediaType.video) {
                    if connection.isVideoMirroringSupported {
                        connection.automaticallyAdjustsVideoMirroring = false
                        connection.isVideoMirrored = true
                    }
                }
                session.commitConfiguration()
            }
        } catch {
            Swift.print("No Available Devices")
        }
    }
    
    //　画面のリサイズ対応
    public func resizeWindow() {
        previewLayer!.frame = bounds
    }
    
}
```

```swift:ViewController
import Cocoa
import AVFoundation

class CameraVC: NSViewController, NSWindowDelegate, AVCaptureFileOutputRecordingDelegate {

    @IBOutlet weak var cameraView: CameraView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        cameraView.session.startRunning()
    }
    
    override func viewWillAppear() {
        self.view.window?.delegate = self
    }
    
    override func viewWillDisappear() {
        cameraView.output.stopRecording()
        cameraView.session.stopRunning()
    }
    
    // 画面のリサイズに対応
    func windowDidResize(_ notification: Notification) {
        cameraView.resizeWindow()
    }
    
    public func startRecording() {
        let fileName = "sample.mov"
        // とりあえず書類フォルダに保存する
        guard let docDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
            return
        }
        let url = docDir.appendingPathComponent(fileName)
        cameraView.output.startRecording(to: url, recordingDelegate: self)
    }
    
    public func stopRecording() {
        cameraView.output.stopRecording()
    }
    
    func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {
        Swift.print("Finish Recording.")
    }
    
}
```