Я создал видео из одного изображения и хочу добавить эффект к этому видео с помощью AVMutableVideoCompositionLayerInstruction
, но после его экспорта окончательное видео не будет плавным.
Вот ссылка на видео
Может кто-нибудь помочь мне сделать это гладко?
Вот мой код:
private func pixelBuffer(fromImage image: CGImage, size: CGSize) throws -> CVPixelBuffer {
let options: CFDictionary = [kCVPixelBufferCGImageCompatibilityKey as String: true, kCVPixelBufferCGBitmapContextCompatibilityKey as String: true] as CFDictionary
var pxbuffer: CVPixelBuffer? = nil
let status = CVPixelBufferCreate(kCFAllocatorDefault, Int(size.width), Int(size.height), kCVPixelFormatType_32ARGB, options, &pxbuffer)
guard let buffer = pxbuffer, status == kCVReturnSuccess else { throw NSError.throwError() }
CVPixelBufferLockBaseAddress(buffer, [])
guard let pxdata = CVPixelBufferGetBaseAddress(buffer) else { throw NSError.throwError() }
let bytesPerRow = CVPixelBufferGetBytesPerRow(buffer)
let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
guard let context = CGContext(data: pxdata, width: Int(size.width), height: Int(size.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: rgbColorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue) else { throw NSError.throwError() }
context.concatenate(CGAffineTransform(rotationAngle: 0))
context.draw(image, in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
CVPixelBufferUnlockBaseAddress(buffer, [])
return buffer
}
private func writeSingleImageToMovie(name: String, image: UIImage, movieLength: TimeInterval, outputFileURL: URL, completion: @escaping (Error?) -> ()) {
do {
let imageSize = image.size
let videoWriter = try AVAssetWriter(outputURL: outputFileURL, fileType: AVFileType.mp4)
let videoSettings: [String: Any] = [AVVideoCodecKey: AVVideoCodecType.h264,
AVVideoWidthKey: imageSize.width,
AVVideoHeightKey: imageSize.height]
let videoWriterInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoSettings)
let adaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: videoWriterInput, sourcePixelBufferAttributes: nil)
if !videoWriter.canAdd(videoWriterInput) { throw NSError.throwError() }
videoWriterInput.expectsMediaDataInRealTime = true
videoWriter.add(videoWriterInput)
videoWriter.startWriting()
let timeScale: Int32 = 600 // recommended in CMTime for movies.
let halfMovieLength = Float64(movieLength/2.0) // videoWriter assumes frame lengths are equal.
let startFrameTime = CMTimeMake(value: 0, timescale: timeScale)
let endFrameTime = CMTimeMakeWithSeconds(halfMovieLength, preferredTimescale: timeScale)
videoWriter.startSession(atSourceTime: startFrameTime)
guard let cgImage = image.cgImage else { throw NSError.throwError() }
let buffer: CVPixelBuffer = try pixelBuffer(fromImage: cgImage, size: imageSize)
while !adaptor.assetWriterInput.isReadyForMoreMediaData { usleep(10) }
adaptor.append(buffer, withPresentationTime: startFrameTime)
while !adaptor.assetWriterInput.isReadyForMoreMediaData { usleep(10) }
adaptor.append(buffer, withPresentationTime: endFrameTime)
videoWriterInput.markAsFinished()
videoWriter.finishWriting {
if videoWriter.error == nil {
self.compositeVideo(videoUrl: outputFileURL, size: imageSize, onCompleted: completion)
} else {
completion(videoWriter.error)
}
}
} catch {
completion(error)
}
}
Этот код добавляет эффект масштаба на видео
private func changeFilename(source: URL, toNewName new: URL) {
do {
if FileManager.default.fileExists(atPath: new.absoluteString) {
try FileManager.default.removeItem(at: new)
}
try FileManager.default.moveItem(at: source, to: new)
} catch {
print(error)
}
}
private func compositeVideo(videoUrl: URL, size: CGSize, onCompleted: @escaping (Error?) -> ()) {
let tmpUrl = URL(fileURLWithPath: NSHomeDirectory() + "/Documents/" + imageVideosDirName).appendingPathComponent("tmp.mp4")
changeFilename(source: videoUrl, toNewName: tmpUrl)
let asset = AVURLAsset(url: tmpUrl)
let composition = AVMutableComposition()
let videoDuration = asset.duration
guard
let compositionTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid),
let assetTrack = asset.tracks(withMediaType: .video).first else {
onCompleted(NSError.throwError(domain: "Cannot create composition track."))
return
}
do {
let timeRange = CMTimeRange(start: .zero, duration: asset.duration)
try compositionTrack.insertTimeRange(timeRange, of: assetTrack, at: .zero)
} catch {
print(error)
onCompleted(error)
return
}
let videoComposition = AVMutableVideoComposition()
videoComposition.renderSize = size
videoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
videoComposition.renderScale = 1
let instructionVideo = AVMutableVideoCompositionInstruction()
instructionVideo.timeRange = CMTimeRange(start: .zero, duration: videoDuration)
videoComposition.instructions = [instructionVideo]
let startTransform = CGAffineTransform(scaleX: 1, y: 1)
let endTransform = CGAffineTransform(scaleX: 1.1, y: 1.1)
// let startTransform = CGAffineTransform(translationX: 0, y: 0)
// let endTransform = CGAffineTransform(translationX: 20, y: 0)
let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionTrack)
instruction.setTransformRamp(fromStart: startTransform, toEnd: endTransform, timeRange: CMTimeRange(start: .zero, duration: videoDuration))
instruction.setOpacityRamp(fromStartOpacity: 1, toEndOpacity: 0, timeRange: CMTimeRange(start: CMTime(seconds: 4, preferredTimescale: 600), duration: CMTime(seconds: 1, preferredTimescale: 600)))
instructionVideo.layerInstructions = [instruction]
print(composition.duration)
guard let export = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality) else {
print("Cannot create export session.")
onCompleted(NSError.throwError(domain: "Cannot create export session."))
return
}
export.videoComposition = videoComposition
export.outputURL = videoUrl
export.outputFileType = .mp4
export.exportAsynchronously {
switch export.status {
case .completed:
onCompleted(nil)
default:
onCompleted(NSError.throwError())
}
}
}