undeclared type lottie lotanimationview how create apple ios swift video compression corrupt

type - IOS Video Compression Swift iOS 8 corrupto video archivo



swift ios documentation (4)

Estoy tratando de comprimir el video tomado con la cámara de los usuarios de UIImagePickerController (no es un video existente sino uno sobre la marcha) para cargarlo en mi servidor y tomarme un poco de tiempo para hacerlo, por lo que un tamaño más pequeño es ideal en lugar de 30 45 mb en nuevas cámaras de calidad.

Aquí está el código para hacer una compresión en swift para iOS 8 y se comprime maravillosamente, voy de 35 mb a 2.1 mb fácilmente.

func convertVideo(inputUrl: NSURL, outputURL: NSURL) { //setup video writer var videoAsset = AVURLAsset(URL: inputUrl, options: nil) as AVAsset var videoTrack = videoAsset.tracksWithMediaType(AVMediaTypeVideo)[0] as AVAssetTrack var videoSize = videoTrack.naturalSize var videoWriterCompressionSettings = Dictionary(dictionaryLiteral:(AVVideoAverageBitRateKey,NSNumber(integer:960000))) var videoWriterSettings = Dictionary(dictionaryLiteral:(AVVideoCodecKey,AVVideoCodecH264), (AVVideoCompressionPropertiesKey,videoWriterCompressionSettings), (AVVideoWidthKey,videoSize.width), (AVVideoHeightKey,videoSize.height)) var videoWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeVideo, outputSettings: videoWriterSettings) videoWriterInput.expectsMediaDataInRealTime = true videoWriterInput.transform = videoTrack.preferredTransform var videoWriter = AVAssetWriter(URL: outputURL, fileType: AVFileTypeQuickTimeMovie, error: nil) videoWriter.addInput(videoWriterInput) var videoReaderSettings: [String:AnyObject] = [kCVPixelBufferPixelFormatTypeKey:kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] var videoReaderOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: videoReaderSettings) var videoReader = AVAssetReader(asset: videoAsset, error: nil) videoReader.addOutput(videoReaderOutput) //setup audio writer var audioWriterInput = AVAssetWriterInput(mediaType: AVMediaTypeAudio, outputSettings: nil) audioWriterInput.expectsMediaDataInRealTime = false videoWriter.addInput(audioWriterInput) //setup audio reader var audioTrack = videoAsset.tracksWithMediaType(AVMediaTypeAudio)[0] as AVAssetTrack var audioReaderOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: nil) as AVAssetReaderOutput var audioReader = AVAssetReader(asset: videoAsset, error: nil) audioReader.addOutput(audioReaderOutput) videoWriter.startWriting() //start writing from video reader videoReader.startReading() videoWriter.startSessionAtSourceTime(kCMTimeZero) //dispatch_queue_t processingQueue = dispatch_queue_create("processingQueue", nil) var queue = dispatch_queue_create("processingQueue", nil) videoWriterInput.requestMediaDataWhenReadyOnQueue(queue, usingBlock: { () -> Void in println("Export starting") while videoWriterInput.readyForMoreMediaData { var sampleBuffer:CMSampleBufferRef! sampleBuffer = videoReaderOutput.copyNextSampleBuffer() if (videoReader.status == AVAssetReaderStatus.Reading && sampleBuffer != nil) { videoWriterInput.appendSampleBuffer(sampleBuffer) } else { videoWriterInput.markAsFinished() if videoReader.status == AVAssetReaderStatus.Completed { if audioReader.status == AVAssetReaderStatus.Reading || audioReader.status == AVAssetReaderStatus.Completed { } else { audioReader.startReading() videoWriter.startSessionAtSourceTime(kCMTimeZero) var queue2 = dispatch_queue_create("processingQueue2", nil) audioWriterInput.requestMediaDataWhenReadyOnQueue(queue2, usingBlock: { () -> Void in while audioWriterInput.readyForMoreMediaData { var sampleBuffer:CMSampleBufferRef! sampleBuffer = audioReaderOutput.copyNextSampleBuffer() println(sampleBuffer == nil) if (audioReader.status == AVAssetReaderStatus.Reading && sampleBuffer != nil) { audioWriterInput.appendSampleBuffer(sampleBuffer) } else { audioWriterInput.markAsFinished() if (audioReader.status == AVAssetReaderStatus.Completed) { videoWriter.finishWritingWithCompletionHandler({ () -> Void in println("Finished writing video asset.") self.videoUrl = outputURL var data = NSData(contentsOfURL: outputURL)! println("Byte Size After Compression: /(data.length / 1048576) mb") println(videoAsset.playable) //Networking().uploadVideo(data, fileName: "Test2") self.dismissViewControllerAnimated(true, completion: nil) }) break } } } }) break } } }// Second if }//first while })// first block // return }

Aquí está el código para mi UIImagePickerController que llama al método de compresión

func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) { // Extract the media type from selection let type = info[UIImagePickerControllerMediaType] as String if (type == kUTTypeMovie) { self.videoUrl = info[UIImagePickerControllerMediaURL] as? NSURL var uploadUrl = NSURL.fileURLWithPath(NSTemporaryDirectory().stringByAppendingPathComponent("captured").stringByAppendingString(".mov")) var data = NSData(contentsOfURL: self.videoUrl!)! println("Size Before Compression: /(data.length / 1048576) mb") self.convertVideo(self.videoUrl!, outputURL: uploadUrl!) // Get the video from the info and set it appropriately. /*self.dismissViewControllerAnimated(true, completion: { () -> Void in //self.next.enabled = true })*/ } }

Como mencioné anteriormente, esto funciona en lo que respecta a la reducción del tamaño del archivo, pero cuando recupero el archivo (aún es de tipo .mov), quicktime no puede reproducirlo. Quicktime intenta convertirlo inicialmente, pero falla a la mitad (1-2 segundos después de abrir el archivo). Incluso probé el archivo de video en AVPlayerController pero no da ninguna información sobre la película, es solo un botón de reproducción sin carga de hormigas y sin ninguna longitud, simplemente "-", donde el tiempo suele estar en el jugador. IE un archivo corrupto que no se reproducirá.

Estoy seguro de que tiene algo que ver con la configuración para escribir el activo, ya sea la escritura del video o la grabación de audio, no estoy seguro del todo. Incluso podría ser la lectura del activo que está causando que se corrompa. He intentado cambiar las variables y configurar diferentes teclas para leer y escribir, pero no he encontrado la combinación correcta y esto apesta que puedo comprimir pero obtener un archivo corrupto. No estoy seguro en absoluto y cualquier ayuda sería apreciada. Pleeeeeeeeease.


¡Lo averigué! Bien, hubo 2 problemas: 1 problema fue con la llamada de la función videoWriter.finishWritingWithCompletionHandler. cuando se ejecuta este bloque de finalización, NO SIGNIFICA que el escritor de video haya terminado de escribir en la URL de salida. Así que tuve que comprobar si el estado se completó antes de cargar el archivo de video real. Es una especie de hack pero esto es lo que hice

videoWriter.finishWritingWithCompletionHandler({() -> Void in while true { if videoWriter.status == .Completed { var data = NSData(contentsOfURL: outputURL)! println("Finished: Byte Size After Compression: /(data.length / 1048576) mb") Networking().uploadVideo(data, fileName: "Video") self.dismissViewControllerAnimated(true, completion: nil) break } } })

El segundo problema que tuve fue un estado de error y fue porque seguí escribiendo en el mismo directorio temporal que se muestra en el código para el método UIImagePickerController didFinishSelectingMediaWithInfo en mi pregunta. Así que acabo de usar la fecha actual como un nombre de directorio para que sea único.

var uploadUrl = NSURL.fileURLWithPath(NSTemporaryDirectory().stringByAppendingPathComponent("/(NSDate())").stringByAppendingString(".mov"))

[EDITAR]: MEJOR SOLUCIÓN

Ok, después de mucha experimentación y meses después, encontré una solución muy buena y mucho más sencilla para bajar un video de 45 mb a 1.42 mb con bastante buena calidad.

A continuación se muestra la función para llamar en lugar de la función convertVideo original. Tenga en cuenta que tuve que escribir mi propio controlador de finalización, al que se llama después de que finalice la exportación asíncrona. Acabo de llamarlo controlador.

func compressVideo(inputURL: NSURL, outputURL: NSURL, handler:(session: AVAssetExportSession)-> Void) { var urlAsset = AVURLAsset(URL: inputURL, options: nil) var exportSession = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPresetMediumQuality) exportSession.outputURL = outputURL exportSession.outputFileType = AVFileTypeQuickTimeMovie exportSession.shouldOptimizeForNetworkUse = true exportSession.exportAsynchronouslyWithCompletionHandler { () -> Void in handler(session: exportSession) } }

Y aquí está el código en la función uiimagepickercontrollerDidFinisPickingMediaWithInfo.

self.compressVideo(inputURL!, outputURL: uploadUrl!, handler: { (handler) -> Void in if handler.status == AVAssetExportSessionStatus.Completed { var data = NSData(contentsOfURL: uploadUrl!) println("File size after compression: /(Double(data!.length / 1048576)) mb") self.picker.dismissViewControllerAnimated(true, completion: nil) } else if handler.status == AVAssetExportSessionStatus.Failed { let alert = UIAlertView(title: "Uh oh", message: " There was a problem compressing the video maybe you can try again later. Error: /(handler.error.localizedDescription)", delegate: nil, cancelButtonTitle: "Okay") alert.show() }) } })



Esta respuesta ha sido completamente reescrita y anotada para ser compatible con Swift 4.0 . Tenga en cuenta que cambiar los valores de AVFileType y presetName permite ajustar la salida final en términos de tamaño y calidad.

import AVFoundation extension ViewController: AVCaptureFileOutputRecordingDelegate { // Delegate function has been updated func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) { // This code just exists for getting the before size. You can remove it from production code do { let data = try Data(contentsOf: outputFileURL) print("File size before compression: /(Double(data.count / 1048576)) mb") } catch { print("Error: /(error)") } // This line creates a generic filename based on UUID, but you may want to use your own // The extension must match with the AVFileType enum let path = NSTemporaryDirectory() + UUID().uuidString + ".m4v" let outputURL = URL.init(fileURLWithPath: path) let urlAsset = AVURLAsset(url: outputURL) // You can change the presetName value to obtain different results if let exportSession = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPresetMediumQuality) { exportSession.outputURL = outputURL // Changing the AVFileType enum gives you different options with // varying size and quality. Just ensure that the file extension // aligns with your choice exportSession.outputFileType = AVFileType.mov exportSession.exportAsynchronously { switch exportSession.status { case .unknown: break case .waiting: break case .exporting: break case .completed: // This code only exists to provide the file size after compression. Should remove this from production code do { let data = try Data(contentsOf: outputFileURL) print("File size after compression: /(Double(data.count / 1048576)) mb") } catch { print("Error: /(error)") } case .failed: break case .cancelled: break } } } } }

A continuación se muestra la respuesta original escrita para Swift 3.0:

extension ViewController: AVCaptureFileOutputRecordingDelegate { func capture(_ captureOutput: AVCaptureFileOutput!, didFinishRecordingToOutputFileAt outputFileURL: URL!, fromConnections connections: [Any]!, error: Error!) { guard let data = NSData(contentsOf: outputFileURL as URL) else { return } print("File size before compression: /(Double(data.length / 1048576)) mb") let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + NSUUID().uuidString + ".m4v") compressVideo(inputURL: outputFileURL as URL, outputURL: compressedURL) { (exportSession) in guard let session = exportSession else { return } switch session.status { case .unknown: break case .waiting: break case .exporting: break case .completed: guard let compressedData = NSData(contentsOf: compressedURL) else { return } print("File size after compression: /(Double(compressedData.length / 1048576)) mb") case .failed: break case .cancelled: break } } } func compressVideo(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 = AVFileTypeQuickTimeMovie exportSession.shouldOptimizeForNetworkUse = true exportSession.exportAsynchronously { () -> Void in handler(exportSession) } } }


Su método de conversión es asíncrono, pero no tiene un bloque de finalización. Entonces, ¿cómo puede saber su código cuando el archivo está listo? Tal vez estés usando el archivo antes de que esté completamente escrito.

La conversión en sí también parece extraña: el audio y el video generalmente se escriben en paralelo, no en serie.

Su relación de compresión milagrosa podría indicar que ha escrito menos marcos de los que realmente cree.