iOS
動画
mp4
Swift
mov

[switf4] iPhoneで撮影した動画をMOVからmp4に変換する

はじめに

この記事ではiPhoneで撮影した動画をMOVファイルからmp4ファイルに変換する方法について記述します

注意
この記事では動画を撮影する機能は実装しません

iPhoneで撮影した動画をMOVからmp4に変換する理由

iPhoneで撮影した動画をHLS形式でアプリ内で再生させる機能を実装していました
その際にiPhone8以上の端末で撮影した動画をHLSに変換をすると
音声は再生されるが、映像が真っ暗やん!!
という問題が発生し、原因を調査してみると撮影した動画をmp4にちゃんと書き出せていなかったことが原因だったためです

開発環境

Xcode :9.3
swift :4.1
iPhone:8以上

実装手順

1、撮影した動画を一時的に保存する場所を指定する
2、撮影した動画をAVAssetExportSessionを用いてmp4に変換する
3、撮影した動画を削除
4、mp4に変換して作成された新しい動画をカメラロールに保存する

実際のソースコード

前提のソースコード

以下のような動画撮影を開始&停止する処理があることを前提とします。

import UIKit
import AVFoundation
import Photos

class VideoShootingViewController: UIViewController, AVCaptureFileOutputRecordingDelegate {

    @IBOutlet weak var startStopButton: UIButton!

    var fileOutput: AVCaptureMovieFileOutput?
    var isRecoding = false

    // セッションのインスタンス生成
    var captureSession = AVCaptureSession()
    var filePath: String?
    var fileURL: URL?

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    // 録画の開始・停止ボタン
    @IBAction func tapStartStopButton(_ sender: Any) {
        if isRecoding { // 録画終了
            self.startStopButton.setTitle("◉", for: .normal)
            self.fileOutput?.stopRecording()

            DispatchQueue.global(qos: .userInitiated).async {
                self.captureSession.stopRunning()
            }
        }
        else{ // 録画開始
            self.startStopButton.setTitle("■", for: .normal)

        }
    }
    isRecoding = !isRecoding
}

1、撮影した動画を一時的に保存する場所を指定する

// 1、撮影した動画を一時的に保存する場所を指定する
let fileName = "\(Date().timeIntervalSince1970).mov"
self.filePath = NSHomeDirectory() + "/tmp/" + fileName
self.fileURL = NSURL(fileURLWithPath: self.filePath!) as URL
self.fileOutput?.startRecording(to: self.fileURL!, recordingDelegate: self as AVCaptureFileOutputRecordingDelegate)

2、撮影した動画をAVAssetExportSessionを用いてmp4に変換する

この処理は撮影を停止した時に呼ばれるAVCaptureFileOutputRecordingDelegateのデリゲートメソッド内に記載します

// 撮影を停止した時に呼ばれる処理
// AVCaptureFileOutputRecordingDelegateのデリゲートメソッド
    func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {

        // 2、撮影した動画をAVAssetExportSessionを用いてmp4に変換する
        let avAsset = AVURLAsset(url: outputFileURL, options: nil)
        let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetHighestQuality)
        exportSession?.outputFileType = AVFileType.mp4
        let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
        let documentsDirectory = paths[0] as String

        let fileName = "\(Date().timeIntervalSince1970).mp4"
        let filePath = "\(documentsDirectory)" + fileName
        self.fileURL = NSURL(fileURLWithPath: filePath) as URL
        exportSession?.outputURL = self.fileURL

        // 正しくmp4ファイルに書き出すことができたかどうか
        exportSession?.exportAsynchronously(completionHandler: {
            switch exportSession?.status {
            case .completed?:
                break

            case .failed?:
                debugPrint("failed: \(String(describing: exportSession?.error))")
                break

            case .cancelled?:
                break

            default:
                break
            }
        })

    }

3、撮影した動画を削除

// 3、撮影した動画を削除
do {
    try FileManager.default.removeItem(atPath: self.filePath!)
} catch {
    //エラー
}

4、mp4に変換して作成された新しい動画をカメラロールに保存する

// 4、mp4に変換して作成された新しい動画をカメラロールに保存する
PHPhotoLibrary.shared().performChanges({
    PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: self.fileURL!)
}) { completed, error in
    if completed {
        debugPrint("Video is saved!")

    }
}

完成形

import UIKit
import AVFoundation
import Photos

class VideoShootingViewController: UIViewController, AVCaptureFileOutputRecordingDelegate {

    @IBOutlet weak var startStopButton: UIButton!

    var isRecoding = false
    var fileOutput: AVCaptureMovieFileOutput?

    // セッションのインスタンス生成
    var captureSession = AVCaptureSession()
    var filePath: String?
    var fileURL: URL?

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) {

        // 2、撮影した動画をAVAssetExportSessionを用いてmp4に変換する
        let avAsset = AVURLAsset(url: outputFileURL, options: nil)
        let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetHighestQuality)
        exportSession?.outputFileType = AVFileType.mp4
        let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
        let documentsDirectory = paths[0] as String

        let fileName = "\(Date().timeIntervalSince1970).mp4"
        let filePath = "\(documentsDirectory)" + fileName
        self.fileURL = NSURL(fileURLWithPath: filePath) as URL
        exportSession?.outputURL = self.fileURL

        exportSession?.exportAsynchronously(completionHandler: {
            switch exportSession?.status {
            case .completed?:
                // 3、撮影した動画を削除
                do {
                    try FileManager.default.removeItem(atPath: self.filePath!)
                } catch {
                    //エラー
                }
                // 4、mp4に変換して作成された新しい動画をカメラロールに保存する
                PHPhotoLibrary.shared().performChanges({
                    PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: self.fileURL!)
                }) { completed, error in
                    if completed {
                        debugPrint("Video is saved!")
                    }
                }
                break

            case .failed?:
                debugPrint("failed: \(String(describing: exportSession?.error))")
                break

            case .cancelled?:
                break

            default:
                break
            }
        })

    }

    // 録画の開始・停止ボタン
    @IBAction func tapStartStopButton(_ sender: Any) {
        if isRecoding { // 録画終了
            self.startStopButton.setTitle("◉", for: .normal)
            self.fileOutput?.stopRecording()

            DispatchQueue.global(qos: .userInitiated).async {
                self.captureSession.stopRunning()
                self.timer.invalidate()
            }


        }
        else{ // 録画開始
            self.startStopButton.setTitle("■", for: .normal)

            // 1、撮影した動画を一時的に保存する場所を指定する
            let fileName = "\(Date().timeIntervalSince1970).mov"
            self.filePath = NSHomeDirectory() + "/tmp/" + fileName
            self.fileURL = NSURL(fileURLWithPath: self.filePath!) as URL
            self.fileOutput?.startRecording(to: self.fileURL!, recordingDelegate: self as AVCaptureFileOutputRecordingDelegate)

        }
        isRecoding = !isRecoding
    }
}

参考記事

Swift で録画したビデオ(mov)を MP4 に変換する

Swiftでのファイル操作

自社紹介

株式会社ManhattanCode