Search code examples
iosobjective-cswiftavfoundationavassetwriter

Value of type DictionaryLiteral<_,_> does not conform to 'Any' in coersion


I am attempting to set up custom video compression in swift based off of this SO post that used Obj-C instead (How can I reduce the file size of a video created with UIImagePickerController?). However, I am having a few issues converting the syntax, specifically the error above which is highlighted over the dictionary. The compression function is below:

func convertVideoToLowQuailty(withInputURL inputURL: URL, outputURL: URL) {
    //setup video writer
    var videoAsset = AVURLAsset(url: inputURL, options: nil)
    var videoTrack = videoAsset.tracks(withMediaType: AVMediaTypeVideo)[0]
    var videoSize = videoTrack.naturalSize
    var videoWriterCompressionSettings = [
        AVVideoAverageBitRateKey : Int(1250000)
    ]

    var videoWriterSettings : NSDictionary = [
        DictionaryLiteral : (Key: AVVideoCodecKey, Object: AVVideoCodecH264),
        AVVideoCompressionPropertiesKey : videoWriterCompressionSettings,
        AVVideoWidthKey : Int(videoSize.width),
        AVVideoHeightKey : Int(videoSize.height)
    ]

    var videoWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoWriterSettings as! [String : Any?])
    videoWriterInput.expectsMediaDataInRealTime = true
    videoWriterInput.transform = videoTrack.preferredTransform
    var videoWriter = try! AVAssetWriter(outputURL: outputURL, fileType: AVFileTypeMPEG4)
    videoWriter.add(videoWriterInput)
    //setup video reader
    var videoReaderSettings = [ (kCVPixelBufferPixelFormatTypeKey as String) : Int(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange) ]
    var videoReaderOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: videoReaderSettings)
    var videoReader = try! AVAssetReader(asset: videoAsset)
    videoReader.add(videoReaderOutput)
    //setup audio writer
    var audioWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeAudio, outputSettings: nil)
    audioWriterInput.expectsMediaDataInRealTime = false
    videoWriter.add(audioWriterInput)
    //setup audio reader
    var audioTrack = videoAsset.tracks(withMediaType: AVMediaTypeAudio)[0]
    var audioReaderOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: nil)
    var audioReader = try! AVAssetReader(asset: videoAsset)
    audioReader.add(audioReaderOutput)
    videoWriter.startWriting()
    //start writing from video reader
    videoReader.startReading()
    videoWriter.startSession(atSourceTime: kCMTimeZero)
    var processingQueue = DispatchQueue(label: "processingQueue1")
    videoWriterInput.requestMediaDataWhenReady(on: processingQueue, using: {() -> Void in
        while videoWriterInput.isReadyForMoreMediaData {
            var sampleBuffer: CMSampleBuffer
            if videoReader.status == .reading && (sampleBuffer == videoReaderOutput.copyNextSampleBuffer()!) {
                videoWriterInput.append(sampleBuffer)

            }
            else {
                videoWriterInput.markAsFinished()
                if videoReader.status == .completed {
                    //start writing from audio reader
                    audioReader.startReading()
                    videoWriter.startSession(atSourceTime: kCMTimeZero)
                    var processingQueue = DispatchQueue(label: "processingQueue2")
                    audioWriterInput.requestMediaDataWhenReady(on: processingQueue, using: {() -> Void in
                        while audioWriterInput.isReadyForMoreMediaData {
                            var sampleBuffer: CMSampleBuffer
                            if audioReader.status == .reading && (sampleBuffer == (audioReaderOutput.copyNextSampleBuffer()!)) {
                                audioWriterInput.append(sampleBuffer)
                            }
                            else {
                                audioWriterInput.markAsFinished()
                                if audioReader.status == .completed {
                                    videoWriter.finishWriting(completionHandler: {() -> Void in
                                        self.sendMovieFile(at: outputURL)
                                    })
                                }
                            }
                        }
                    })
                }
            }

        }
    })
}

Solution

  • I do not understand why you need this line:

        DictionaryLiteral : (Key: AVVideoCodecKey, Object: AVVideoCodecH264),
    

    Seeing the linked thread, you can write something like this:

        var videoWriterCompressionSettings: [String: AnyObject] = [
            AVVideoAverageBitRateKey : 1250000 as NSNumber
        ]
    
        var videoWriterSettings : [String: AnyObject] = [
            AVVideoCodecKey: AVVideoCodecH264 as NSString,
            AVVideoCompressionPropertiesKey : videoWriterCompressionSettings as NSDictionary,
            AVVideoWidthKey : videoSize.width as NSNumber,
            AVVideoHeightKey : videoSize.height as NSNumber
        ]
    
        var videoWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoWriterSettings)
    

    (Someone prefers [String: Any] than [String: AnyObject], saying it's more Swifty in Swift 3. With using Any, you can remove some castings, but may mistakingly contain some bad things which would be revealed only in runtime.)

    And another very bad part of your code is as! [String : Any?]. You need to pass [String: Any]? to AVAssetWriterInput.init(mediaType:outputSettings:), not [String: Any?].

    (There may be some other bad parts, which I have not checked...)