Как сжать видео в swift? - PullRequest
11 голосов
/ 02 июня 2019

TLDR: Перейти к обновлениям. Я ищу способ сжатия или понижения качества видеоизображения, желательно не сразу после создания, но если это единственный способ, пусть будет так

Также, если вы знаете о каких-либо хороших какао-бобах, которые могут это сделать, это было бы хорошо.

Обновление 3:

Я ищу функцию, которая может выводить сжатый URL, и я должен иметь возможность контролировать качество сжатия ...

Обновление 2:

После попытки заставить функцию работать в ее текущем состоянии, она не работает. Приносит ноль. Я думаю в результате следующее:

                let outputURL = urlToCompress
            assetWriter = try AVAssetWriter(outputURL: outputURL, fileType: AVFileType.mov)

Я пытаюсь сжать видео по-быстрому. До сих пор все решения для этого были для использования при создании. Мне интересно, есть ли способ сжатия после создания? только используя URL видео?

Если нет, то как я могу создать функцию сжатия, которая сжимает видео и возвращает сжатый URL?

Код, с которым я работал:

func compressVideo(videoURL: URL) -> URL {
        let data = NSData(contentsOf: videoURL as URL)!
        print("File size before compression: \(Double(data.length / 1048576)) mb")
        let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + NSUUID().uuidString + ".mov")
        compressVideoHelperMethod(inputURL: videoURL , outputURL: compressedURL) { (exportSession) in

        }
        return compressedURL
    }

    func compressVideoHelperMethod(inputURL: URL, outputURL: URL, handler:@escaping (_ exportSession: AVAssetExportSession?)-> Void) {
        let urlAsset = AVURLAsset(url: inputURL, options: nil)
        guard let exportSession = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPresetMediumQuality) else {
            handler(nil)

            return
        }

        exportSession.outputURL = outputURL
        exportSession.outputFileType = AVFileType.mov
        exportSession.shouldOptimizeForNetworkUse = true
        exportSession.exportAsynchronously { () -> Void in
            handler(exportSession)
        }
    }

Обновление:

Итак, я нашел код ниже. Я еще не тестировал его, но я не знаю, как сделать так, чтобы я выбрал качество сжатия:

var assetWriter:AVAssetWriter?
var assetReader:AVAssetReader?
let bitrate:NSNumber = NSNumber(value:250000)

func compressFile(urlToCompress: URL, outputURL: URL, completion:@escaping (URL)->Void){
    //video file to make the asset

    var audioFinished = false
    var videoFinished = false

    let asset = AVAsset(url: urlToCompress);

    let duration = asset.duration
    let durationTime = CMTimeGetSeconds(duration)

    print("Video Actual Duration -- \(durationTime)")

    //create asset reader
    do{
        assetReader = try AVAssetReader(asset: asset)
    } catch{
        assetReader = nil
    }

    guard let reader = assetReader else{
        fatalError("Could not initalize asset reader probably failed its try catch")
    }

    let videoTrack = asset.tracks(withMediaType: AVMediaType.video).first!
    let audioTrack = asset.tracks(withMediaType: AVMediaType.audio).first!

    let videoReaderSettings: [String:Any] =  [(kCVPixelBufferPixelFormatTypeKey as String?)!:kCVPixelFormatType_32ARGB ]

    // ADJUST BIT RATE OF VIDEO HERE

    if #available(iOS 11.0, *) {
        let videoSettings:[String:Any] = [
            AVVideoCompressionPropertiesKey: [AVVideoAverageBitRateKey:self.bitrate],
            AVVideoCodecKey: AVVideoCodecType.h264,
            AVVideoHeightKey: videoTrack.naturalSize.height,
            AVVideoWidthKey: videoTrack.naturalSize.width
        ]
    } else {
        // Fallback on earlier versions
    }


    let assetReaderVideoOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: videoReaderSettings)
    let assetReaderAudioOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: nil)


    if reader.canAdd(assetReaderVideoOutput){
        reader.add(assetReaderVideoOutput)
    }else{
        fatalError("Couldn't add video output reader")
    }

    if reader.canAdd(assetReaderAudioOutput){
        reader.add(assetReaderAudioOutput)
    }else{
        fatalError("Couldn't add audio output reader")
    }

    let audioInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: nil)
    let videoInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoReaderSettings)
    videoInput.transform = videoTrack.preferredTransform
    //we need to add samples to the video input

    let videoInputQueue = DispatchQueue(label: "videoQueue")
    let audioInputQueue = DispatchQueue(label: "audioQueue")

    do{
        assetWriter = try AVAssetWriter(outputURL: outputURL, fileType: AVFileType.mov)
    }catch{
        assetWriter = nil
    }
    guard let writer = assetWriter else{
        fatalError("assetWriter was nil")
    }

    writer.shouldOptimizeForNetworkUse = true
    writer.add(videoInput)
    writer.add(audioInput)


    writer.startWriting()
    reader.startReading()
    writer.startSession(atSourceTime: CMTime.zero)


    let closeWriter:()->Void = {
        if (audioFinished && videoFinished){
            self.assetWriter?.finishWriting(completionHandler: {
                print("------ Finish Video Compressing")                    
                completion((self.assetWriter?.outputURL)!)
            })

            self.assetReader?.cancelReading()
        }
    }


    audioInput.requestMediaDataWhenReady(on: audioInputQueue) {
        while(audioInput.isReadyForMoreMediaData){
            let sample = assetReaderAudioOutput.copyNextSampleBuffer()
            if (sample != nil){
                audioInput.append(sample!)
            }else{
                audioInput.markAsFinished()
                DispatchQueue.main.async {
                    audioFinished = true
                    closeWriter()
                }
                break;
            }
        }
    }

    videoInput.requestMediaDataWhenReady(on: videoInputQueue) {
        //request data here
        while(videoInput.isReadyForMoreMediaData){
            let sample = assetReaderVideoOutput.copyNextSampleBuffer()
            if (sample != nil){
                let timeStamp = CMSampleBufferGetPresentationTimeStamp(sample!)
                let timeSecond = CMTimeGetSeconds(timeStamp)
                let per = timeSecond / durationTime
                print("Duration --- \(per)")
                videoInput.append(sample!)
            }else{
                videoInput.markAsFinished()
                DispatchQueue.main.async {
                    videoFinished = true
                    closeWriter()
                }
                break;
            }
        }
    }
}

Как я могу изменить это, чтобы иметь возможность установить качество? Я ищу сжатие около 0,6

Сейчас я играюсь со следующим кодом, проблема в том, что он продолжает печатать ошибку (похоже, не работает):

    func convertVideoToLowQuailty(withInputURL inputURL: URL?, outputURL: URL?, handler: @escaping (AVAssetExportSession?) -> Void) {
    do {
        if let outputURL = outputURL {
            try FileManager.default.removeItem(at: outputURL)
        }
    } catch {
    }
    var asset: AVURLAsset? = nil
    if let inputURL = inputURL {
        asset = AVURLAsset(url: inputURL, options: nil)
    }
    var exportSession: AVAssetExportSession? = nil
    if let asset = asset {
        exportSession = AVAssetExportSession(asset: asset, presetName:AVAssetExportPresetMediumQuality)
    }
    exportSession?.outputURL = outputURL
    exportSession?.outputFileType = .mov
    exportSession?.exportAsynchronously(completionHandler: {
        handler(exportSession)
    })
}

func compressVideo(videoURL: URL) -> URL {
    var outputURL = URL(fileURLWithPath: "/Users/alexramirezblonski/Desktop/output.mov")
    convertVideoToLowQuailty(withInputURL: videoURL, outputURL: outputURL, handler: { exportSession in
        print("fdshljfhdlasjkfdhsfsdljk")
        if exportSession?.status == .completed {
            print("completed\n", exportSession!.outputURL!)
            outputURL = exportSession!.outputURL!
        } else {
            print("error\n")
            outputURL = exportSession!.outputURL!//this needs to be fixed and may cause errors
        }
    })
    return outputURL
}

1 Ответ

3 голосов
/ 10 июня 2019

Я посмотрел на ваш код. На самом деле, вы сжимаете видео в среднем качестве, которое будет примерно таким же, как оригинальное видео, которое у вас есть. Таким образом, вы должны изменить presetName при инициализации сеанса экспорта следующим образом:

exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetLowQuality)

Вы можете передать AVAssetExportPresetMediumQuality, так что это может быть сжато, как вы ожидаете.

Существует следующий список форматов, доступных для сжатия видео.

1. Доступно с iOS 11.0

  • AVAssetExportPresetHEVCHighestQuality
  • AVAssetExportPresetHEVC1920x1080
  • AVAssetExportPresetHEVC3840x2160

2. Доступно с iOS 4.0

  • AVAssetExportPresetLowQuality
  • AVAssetExportPresetMediumQuality
  • AVAssetExportPresetHighestQuality
  • AVAssetExportPreset640x480
  • AVAssetExportPreset960x540
  • AVAssetExportPreset1280x720
  • AVAssetExportPreset1920x1080
  • AVAssetExportPreset3840x2160
  • AVAssetExportPresetAppleM4A

Вы можете использовать прежде всего формат для сжатия вашего видео в соответствии с вашими требованиями. Я надеюсь, что это поможет вам.

...