Добавьте водяной знак в видео после объединения видео и аудио активов в один из Swift3 iOS

Я работаю над приложениями на основе Video в Swift3 . В соответствии с моим требованием я должен объединить Video и Audio AVAsset в один, отрегулировать их объемы отдельно и сохранить окончательную Video в iPhone Device gallery . Это работает отлично, используя код ниже:

 func mergeVideoAndMusicWithVolume(assetVideo: AVAsset, assetMusic: AVAsset, startAudioTime: Float64, volumeVideo: Float, volumeAudio: Float){ //To merging a video and a music and set it a volume let dirPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true) let docsDir = dirPaths[0] as String let composition: AVMutableComposition = AVMutableComposition() let compositionVideo: AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID: CMPersistentTrackID()) let compositionAudioVideo: AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID()) let compositionAudioMusic: AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID()) //Add video to the final record do { try compositionVideo.insertTimeRange(CMTimeRangeMake(kCMTimeZero, assetVideo.duration), of: assetVideo.tracks(withMediaType: AVMediaTypeVideo)[0], at: kCMTimeZero) } catch _ { } //Extract audio from the video and the music let audioMix: AVMutableAudioMix = AVMutableAudioMix() var audioMixParam: [AVMutableAudioMixInputParameters] = [] let assetVideoTrack: AVAssetTrack = assetVideo.tracks(withMediaType: AVMediaTypeAudio)[0] let assetMusicTrack: AVAssetTrack = assetMusic.tracks(withMediaType: AVMediaTypeAudio)[0] let videoParam: AVMutableAudioMixInputParameters = AVMutableAudioMixInputParameters(track: assetVideoTrack) videoParam.trackID = compositionAudioVideo.trackID let musicParam: AVMutableAudioMixInputParameters = AVMutableAudioMixInputParameters(track: assetMusicTrack) musicParam.trackID = compositionAudioMusic.trackID //Set final volume of the audio record and the music videoParam.setVolume(volumeVideo, at: kCMTimeZero) musicParam.setVolume(volumeAudio, at: kCMTimeZero) //Add setting audioMixParam.append(musicParam) audioMixParam.append(videoParam) //Add audio on final record //First: the audio of the record and Second: the music do { try compositionAudioVideo.insertTimeRange(CMTimeRangeMake(kCMTimeZero, assetVideo.duration), of: assetVideoTrack, at: kCMTimeZero) } catch _ { assertionFailure() } do { try compositionAudioMusic.insertTimeRange(CMTimeRangeMake(CMTimeMake(Int64(startAudioTime * 10000), 10000), assetVideo.duration), of: assetMusicTrack, at: kCMTimeZero) } catch _ { assertionFailure() } //Add parameter audioMix.inputParameters = audioMixParam //Remove the previous temp video if exist let filemgr = FileManager.default do { if filemgr.fileExists(atPath: "\(docsDir)/movie-merge-music.mov") { try filemgr.removeItem(atPath: "\(docsDir)/movie-merge-music.mov") } else { } } catch _ { } //Exporte the final record' let completeMovie = "\(docsDir)/movie-merge-music.mov" let completeMovieUrl = NSURL(fileURLWithPath: completeMovie) let exporter: AVAssetExportSession = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality)! exporter.outputURL = completeMovieUrl as URL exporter.outputFileType = AVFileTypeMPEG4 exporter.audioMix = audioMix exporter.exportAsynchronously(completionHandler: { DispatchQueue.main.async { _ in self.exportDidFinish(exporter) } }) } func exportDidFinish(_ session: AVAssetExportSession) { if session.status == AVAssetExportSessionStatus.completed { let outputURL = session.outputURL PHPhotoLibrary.shared().performChanges({ PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputURL!) }) { saved, error in if saved { let alertController = UIAlertController(title: "Your video was successfully saved", message: nil, preferredStyle: .alert) let defaultAction = UIAlertAction(title: "OK", style: .default, handler: nil) alertController.addAction(defaultAction) self.present(alertController, animated: true, completion: nil) } } } } 

Моя проблема: теперь я должен добавить изображение Watermark в окончательное Video прежде чем сохранять его в Device gallery .

Я проверил Swift 3: Как добавить водяной знак на видео? Ошибка AVVideoCompositionCoreAnimationTool iOS 10, но после применения водяного знака видео идет в небольшой области кадра.

Примечание. Вот мой class, который я использую для добавления Watermark:

 import Foundation import UIKit import AssetsLibrary import AVFoundation import Photos enum QUWatermarkPosition { case TopLeft case TopRight case BottomLeft case BottomRight case Default } class VideoWatermarkManager: NSObject { func watermark(video videoAsset:AVAsset, watermarkText text : String, saveToLibrary flag : Bool, watermarkPosition position : QUWatermarkPosition, completion : ((_ status : AVAssetExportSessionStatus?, _ session: AVAssetExportSession?, _ outputURL : URL?) -> ())?) { self.watermark(video: videoAsset, watermarkText: text, imageName: nil, saveToLibrary: flag, watermarkPosition: position) { (status, session, outputURL) -> () in completion!(status, session, outputURL) } } func watermark(video videoAsset:AVAsset, imageName name : String, saveToLibrary flag : Bool, watermarkPosition position : QUWatermarkPosition, completion : ((_ status : AVAssetExportSessionStatus?, _ session: AVAssetExportSession?, _ outputURL : URL?) -> ())?) { self.watermark(video: videoAsset, watermarkText: nil, imageName: name, saveToLibrary: flag, watermarkPosition: position) { (status, session, outputURL) -> () in completion!(status, session, outputURL) } } private func watermark(video videoAsset:AVAsset, watermarkText text : String!, imageName name : String!, saveToLibrary flag : Bool, watermarkPosition position : QUWatermarkPosition, completion : ((_ status : AVAssetExportSessionStatus?, _ session: AVAssetExportSession?, _ outputURL : URL?) -> ())?) { DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async { let mixComposition = AVMutableComposition() let compositionVideoTrack = mixComposition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) let clipVideoTrack = videoAsset.tracks(withMediaType: AVMediaTypeVideo)[0] do { try compositionVideoTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, videoAsset.duration), of: clipVideoTrack, at: kCMTimeZero) } catch { print(error.localizedDescription) } let videoSize = clipVideoTrack.naturalSize let parentLayer = CALayer() let videoLayer = CALayer() parentLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height) videoLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height) parentLayer.addSublayer(videoLayer) let watermarkImage = UIImage(named: name) let imageLayer = CALayer() imageLayer.contents = watermarkImage?.cgImage var xPosition : CGFloat = 0.0 var yPosition : CGFloat = 0.0 let imageSize : CGFloat = 57.0 switch (position) { case .TopLeft: xPosition = 0 yPosition = 0 break case .TopRight: xPosition = videoSize.width - imageSize yPosition = 0 break case .BottomLeft: xPosition = 0 yPosition = videoSize.height - imageSize break case .BottomRight, .Default: xPosition = videoSize.width - imageSize yPosition = videoSize.height - imageSize break } imageLayer.frame = CGRect(x: xPosition, y: yPosition, width: imageSize, height: imageSize) imageLayer.opacity = 0.85 parentLayer.addSublayer(imageLayer) let videoComp = AVMutableVideoComposition() videoComp.renderSize = videoSize videoComp.frameDuration = CMTimeMake(1, 30) videoComp.renderScale = 1.0 videoComp.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, in: parentLayer) let instruction = AVMutableVideoCompositionInstruction() instruction.timeRange = CMTimeRangeMake(kCMTimeZero, mixComposition.duration) _ = mixComposition.tracks(withMediaType: AVMediaTypeVideo)[0] as AVAssetTrack let layerInstruction = self.videoCompositionInstructionForTrack(track: compositionVideoTrack, asset: videoAsset) instruction.layerInstructions = [layerInstruction] videoComp.instructions = [instruction] let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] let dateFormatter = DateFormatter() dateFormatter.dateStyle = .long dateFormatter.timeStyle = .short let date = dateFormatter.string(from: Date()) let url = URL(fileURLWithPath: documentDirectory).appendingPathComponent("watermarkVideo-\(date).mov") let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality) exporter?.outputURL = url exporter?.outputFileType = AVFileTypeQuickTimeMovie exporter?.shouldOptimizeForNetworkUse = true exporter?.videoComposition = videoComp exporter?.exportAsynchronously() { DispatchQueue.main.async { if exporter?.status == AVAssetExportSessionStatus.completed { let outputURL = exporter?.outputURL if flag { // Save to library // let library = ALAssetsLibrary() if UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(outputURL!.path) { PHPhotoLibrary.shared().performChanges({ PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputURL!) }) { saved, error in if saved { completion!(AVAssetExportSessionStatus.completed, exporter, outputURL) } } } // if library.videoAtPathIs(compatibleWithSavedPhotosAlbum: outputURL) { // library.writeVideoAtPathToSavedPhotosAlbum(outputURL, // completionBlock: { (assetURL:NSURL!, error:NSError!) -> Void in // // completion!(AVAssetExportSessionStatus.Completed, exporter, outputURL) // }) // } } else { completion!(AVAssetExportSessionStatus.completed, exporter, outputURL) } } else { // Error completion!(exporter?.status, exporter, nil) } } } } } private func orientationFromTransform(transform: CGAffineTransform) -> (orientation: UIImageOrientation, isPortrait: Bool) { var assetOrientation = UIImageOrientation.up var isPortrait = false if transform.a == 0 && transform.b == 1.0 && transform.c == -1.0 && transform.d == 0 { assetOrientation = .right isPortrait = true } else if transform.a == 0 && transform.b == -1.0 && transform.c == 1.0 && transform.d == 0 { assetOrientation = .left isPortrait = true } else if transform.a == 1.0 && transform.b == 0 && transform.c == 0 && transform.d == 1.0 { assetOrientation = .up } else if transform.a == -1.0 && transform.b == 0 && transform.c == 0 && transform.d == -1.0 { assetOrientation = .down } return (assetOrientation, isPortrait) } private func videoCompositionInstructionForTrack(track: AVCompositionTrack, asset: AVAsset) -> AVMutableVideoCompositionLayerInstruction { let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track) let assetTrack = asset.tracks(withMediaType: AVMediaTypeVideo)[0] let transform = assetTrack.preferredTransform let assetInfo = orientationFromTransform(transform: transform) var scaleToFitRatio = UIScreen.main.bounds.width / assetTrack.naturalSize.width if assetInfo.isPortrait { scaleToFitRatio = UIScreen.main.bounds.width / assetTrack.naturalSize.height let scaleFactor = CGAffineTransform(scaleX: scaleToFitRatio, y: scaleToFitRatio) instruction.setTransform(assetTrack.preferredTransform.concatenating(scaleFactor), at: kCMTimeZero) } else { let scaleFactor = CGAffineTransform(scaleX: scaleToFitRatio, y: scaleToFitRatio) var concat = assetTrack.preferredTransform.concatenating(scaleFactor).concatenating(CGAffineTransform(translationX: 0, y: UIScreen.main.bounds.width / 2)) if assetInfo.orientation == .down { let fixUpsideDown = CGAffineTransform(rotationAngle: CGFloat(Double.pi)) let windowBounds = UIScreen.main.bounds let yFix = assetTrack.naturalSize.height + windowBounds.height let centerFix = CGAffineTransform(translationX: assetTrack.naturalSize.width, y: yFix) concat = fixUpsideDown.concatenating(centerFix).concatenating(scaleFactor) } instruction.setTransform(concat, at: kCMTimeZero) } return instruction } } 

Может ли кто-нибудь помочь мне, как я могу добавить Watermark в свое Video ?

Я работал над проектом и использовал этот код. Возможно, это поможет вам добавить водяной знак.

 import UIKit import AssetsLibrary import AVFoundation import Photos import SpriteKit enum PDWatermarkPosition { case TopLeft case TopRight case BottomLeft case BottomRight case Default } class PDVideoWaterMarker: NSObject { func watermark(video videoAsset:AVAsset, watermarkText text : String, saveToLibrary flag : Bool, watermarkPosition position : PDWatermarkPosition, completion : ((_ status : AVAssetExportSessionStatus?, _ session: AVAssetExportSession?, _ outputURL : URL?) -> ())?) { self.watermark(video: videoAsset, watermarkText: text, imageName: nil, saveToLibrary: flag, watermarkPosition: position) { (status, session, outputURL) -> () in completion!(status, session, outputURL) } } func watermark(video videoAsset:AVAsset, imageName name : String, watermarkText text : String , saveToLibrary flag : Bool, watermarkPosition position : PDWatermarkPosition, completion : ((_ status : AVAssetExportSessionStatus?, _ session: AVAssetExportSession?, _ outputURL : URL?) -> ())?) { self.watermark(video: videoAsset, watermarkText: text, imageName: name, saveToLibrary: flag, watermarkPosition: position) { (status, session, outputURL) -> () in completion!(status, session, outputURL) } } private func watermark(video videoAsset:AVAsset, watermarkText text : String!, imageName name : String!, saveToLibrary flag : Bool, watermarkPosition position : PDWatermarkPosition, completion : ((_ status : AVAssetExportSessionStatus?, _ session: AVAssetExportSession?, _ outputURL : URL?) -> ())?) { DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async { let mixComposition = AVMutableComposition() let compositionVideoTrack = mixComposition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) if videoAsset.tracks(withMediaType: AVMediaTypeVideo).count == 0 { completion!(nil, nil, nil) return } let clipVideoTrack = videoAsset.tracks(withMediaType: AVMediaTypeVideo)[0] self.addAudioTrack(composition: mixComposition, videoAsset: videoAsset as! AVURLAsset) do { try compositionVideoTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, videoAsset.duration), of: clipVideoTrack, at: kCMTimeZero) } catch { print(error.localizedDescription) } let videoSize = clipVideoTrack.naturalSize //CGSize(width: 375, height: 300) print("videoSize--\(videoSize)") let parentLayer = CALayer() let videoLayer = CALayer() parentLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height) videoLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height) //videoLayer.backgroundColor = UIColor.red.cgColor parentLayer.addSublayer(videoLayer) if name != nil { let watermarkImage = UIImage(named: name) let imageLayer = CALayer() //imageLayer.backgroundColor = UIColor.purple.cgColor imageLayer.contents = watermarkImage?.cgImage var xPosition : CGFloat = 0.0 var yPosition : CGFloat = 0.0 let imageSize : CGFloat = 57.0 switch (position) { case .TopLeft: xPosition = 0 yPosition = 0 break case .TopRight: xPosition = videoSize.width - imageSize - 30 yPosition = 30 break case .BottomLeft: xPosition = 0 yPosition = videoSize.height - imageSize break case .BottomRight, .Default: xPosition = videoSize.width - imageSize yPosition = videoSize.height - imageSize break } imageLayer.frame = CGRect(x: xPosition, y: yPosition, width: imageSize, height: imageSize) imageLayer.opacity = 0.65 parentLayer.addSublayer(imageLayer) if text != nil { let titleLayer = CATextLayer() titleLayer.backgroundColor = UIColor.clear.cgColor titleLayer.string = text titleLayer.font = "Helvetica" as CFTypeRef titleLayer.fontSize = 20 titleLayer.alignmentMode = kCAAlignmentRight titleLayer.frame = CGRect(x: 0, y: yPosition - imageSize, width: videoSize.width - imageSize/2 - 4, height: 57) titleLayer.foregroundColor = UIColor.red.cgColor parentLayer.addSublayer(titleLayer) } } let videoComp = AVMutableVideoComposition() videoComp.renderSize = videoSize videoComp.frameDuration = CMTimeMake(1, 30) videoComp.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, in: parentLayer) let instruction = AVMutableVideoCompositionInstruction() instruction.timeRange = CMTimeRangeMake(kCMTimeZero, mixComposition.duration) instruction.backgroundColor = UIColor.gray.cgColor _ = mixComposition.tracks(withMediaType: AVMediaTypeVideo)[0] as AVAssetTrack let layerInstruction = self.videoCompositionInstructionForTrack(track: compositionVideoTrack, asset: videoAsset) instruction.layerInstructions = [layerInstruction] videoComp.instructions = [instruction] let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] let dateFormatter = DateFormatter() dateFormatter.dateStyle = .long dateFormatter.timeStyle = .short let date = dateFormatter.string(from: Date()) let url = URL(fileURLWithPath: documentDirectory).appendingPathComponent("watermarkVideo-\(date).mov") let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality) exporter?.outputURL = url exporter?.outputFileType = AVFileTypeQuickTimeMovie exporter?.shouldOptimizeForNetworkUse = false exporter?.videoComposition = videoComp exporter?.exportAsynchronously() { DispatchQueue.main.async { if exporter?.status == AVAssetExportSessionStatus.completed { let outputURL = exporter?.outputURL if flag { if UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(outputURL!.path) { PHPhotoLibrary.shared().performChanges({ PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputURL!) }) { saved, error in if saved { completion!(AVAssetExportSessionStatus.completed, exporter, outputURL) } } } } else { completion!(AVAssetExportSessionStatus.completed, exporter, outputURL) } } else { // Error completion!(exporter?.status, exporter, nil) } } } } } private func addAudioTrack(composition: AVMutableComposition, videoAsset: AVURLAsset) { let compositionAudioTrack:AVMutableCompositionTrack = composition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID()) let audioTracks = videoAsset.tracks(withMediaType: AVMediaTypeAudio) for audioTrack in audioTracks { try! compositionAudioTrack.insertTimeRange(audioTrack.timeRange, of: audioTrack, at: kCMTimeZero) } } private func orientationFromTransform(transform: CGAffineTransform) -> (orientation: UIImageOrientation, isPortrait: Bool) { var assetOrientation = UIImageOrientation.up var isPortrait = false if transform.a == 0 && transform.b == 1.0 && transform.c == -1.0 && transform.d == 0 { assetOrientation = .right isPortrait = true } else if transform.a == 0 && transform.b == -1.0 && transform.c == 1.0 && transform.d == 0 { assetOrientation = .left isPortrait = true } else if transform.a == 1.0 && transform.b == 0 && transform.c == 0 && transform.d == 1.0 { assetOrientation = .up } else if transform.a == -1.0 && transform.b == 0 && transform.c == 0 && transform.d == -1.0 { assetOrientation = .down } return (assetOrientation, isPortrait) } private func videoCompositionInstructionForTrack(track: AVCompositionTrack, asset: AVAsset) -> AVMutableVideoCompositionLayerInstruction { let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track) let assetTrack = asset.tracks(withMediaType: AVMediaTypeVideo)[0] let transform = assetTrack.preferredTransform let assetInfo = orientationFromTransform(transform: transform) var scaleToFitRatio = UIScreen.main.bounds.width / 375 if assetInfo.isPortrait { scaleToFitRatio = UIScreen.main.bounds.width / assetTrack.naturalSize.height let scaleFactor = CGAffineTransform(scaleX: scaleToFitRatio, y: scaleToFitRatio) instruction.setTransform(assetTrack.preferredTransform.concatenating(scaleFactor), at: kCMTimeZero) } else { let scaleFactor = CGAffineTransform(scaleX: scaleToFitRatio, y: scaleToFitRatio) var concat = assetTrack.preferredTransform.concatenating(scaleFactor).concatenating(CGAffineTransform(translationX: 0, y: 0)) if assetInfo.orientation == .down { let fixUpsideDown = CGAffineTransform(rotationAngle: CGFloat(Double.pi)) let windowBounds = UIScreen.main.bounds let yFix = 375 + windowBounds.height let centerFix = CGAffineTransform(translationX: assetTrack.naturalSize.width, y: CGFloat(yFix)) concat = fixUpsideDown.concatenating(centerFix).concatenating(scaleFactor) } instruction.setTransform(concat, at: kCMTimeZero) } return instruction } } 

@ Ответ Рахула кажется многообещающим, но после внедрения кода и его использования в течение нескольких дней я заметил, что время от времени у него какие-то странные ошибки. (не беспокойтесь, sh * t)

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

https://rubygarage.org/blog/open-source-image-and-video-processing-library-for-ios

Он также использует как GPU, так и CPU для оптимизации производительности.

  • Зеленая полоса поверх каждого (сетевого) видео, искаженные цвета тоже
  • WebView и HTML5
  • Невозможно воспроизвести ни видео IE10 HTML5, ни видео приложений с современным интерфейсом
  • Только 360p на Youtube с HTML5
  • Как повернуть файл видео (MOV) в Windows?
  • Как передать статический видеофайл
  • Ffmpeg Audio Stereo to Mono с использованием только левого канала
  • Поиск более простой альтернативы видеочата в Skype
  • Добавление других видеокодеков / DVD-поддержки в JavaFX 2.2
  • Использование FFmpeg в .net?
  • Пакетное преобразование папки .mkv-файлов в файлы .m4v с использованием ffmpeg в MAC OS X
  • Давайте будем гением компьютера.