2017-07-19 2 views
1

Ich versuche AVVideoComposition zu verwenden, um Text über einem Video hinzuzufügen und das Video zu speichern. Dies ist der Code, den ich verwenden:Swift 3: So exportieren Sie Video mit Text mit AVVideoComposition

IErstellen Sie ein AVMutableComposition and AVVideoComposition

var mutableComp =   AVMutableComposition() 
var mutableVidComp =  AVMutableVideoComposition() 
var compositionSize :  CGSize? 

func configureAsset(){ 

    let options =    [AVURLAssetPreferPreciseDurationAndTimingKey : "true"] 
    let videoAsset =    AVURLAsset(url: Bundle.main.url(forResource: "Car", withExtension: "mp4")! , options : options) 
    let videoAssetSourceTrack = videoAsset.tracks(withMediaType: AVMediaTypeVideo).first! as AVAssetTrack 

    compositionSize = videoAssetSourceTrack.naturalSize 

    let mutableVidTrack =  mutableComp.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID: kCMPersistentTrackID_Invalid) 
    let trackRange =   CMTimeRangeMake(kCMTimeZero, videoAsset.duration) 

    do { 
     try mutableVidTrack.insertTimeRange(trackRange, of: videoAssetSourceTrack, at: kCMTimeZero) 

     mutableVidTrack.preferredTransform = videoAssetSourceTrack.preferredTransform 

    }catch { print(error) } 

    snapshot =  mutableComp 
    mutableVidComp = AVMutableVideoComposition(propertiesOf: videoAsset) 
} 

IISetup, um die Schichten

func applyVideoEffectsToComposition() { 

    // 1 - Set up the text layer 
    let subTitle1Text =   CATextLayer() 
    subTitle1Text.font =   "Helvetica-Bold" as CFTypeRef 
    subTitle1Text.frame =   CGRect(x: self.view.frame.midX - 60 , y: self.view.frame.midY - 50, width: 120, height: 100) 
    subTitle1Text.string =   "Bench" 
    subTitle1Text.foregroundColor = UIColor.black.cgColor 
    subTitle1Text.alignmentMode = kCAAlignmentCenter 

    // 2 - The usual overlay 
    let overlayLayer = CALayer() 
    overlayLayer.addSublayer(subTitle1Text) 
    overlayLayer.frame = CGRect(x: 0, y: 0, width: compositionSize!.width, height: compositionSize!.height) 
    overlayLayer.masksToBounds = true 


    // 3 - set up the parent layer 
    let parentLayer = CALayer() 
    let videoLayer = CALayer() 
    parentLayer.frame = CGRect(x: 0, y: 0, width: compositionSize!.width, height: compositionSize!.height) 
    videoLayer.frame = CGRect(x: 0, y: 0, width: compositionSize!.width, height: compositionSize!.height) 

    parentLayer.addSublayer(videoLayer) 
    parentLayer.addSublayer(overlayLayer) 

    mutableVidComp.animationTool = AVVideoCompositionCoreAnimationTool(postProcessingAsVideoLayer: videoLayer, in: parentLayer) 

} 

III. Speicher Video mit AVMutbaleVideoComposition

func saveAsset(){ 

func deleteFile(_ filePath:URL) { 

guard FileManager.default.fileExists(atPath: filePath.path) else { return } 

do { 
try FileManager.default.removeItem(atPath: filePath.path) } 
catch {fatalError("Unable to delete file: \(error) : \(#function).")} } 


let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as URL 
let filePath =   documentsDirectory.appendingPathComponent("rendered-audio.mp4") 
deleteFile(filePath) 

if let exportSession = AVAssetExportSession(asset: mutableComp , presetName: AVAssetExportPresetHighestQuality){ 

    exportSession.videoComposition = mutableVidComp 

// exportSession.canPerformMultiplePassesOverSourceMediaData = true 
exportSession.outputURL =     filePath 
exportSession.shouldOptimizeForNetworkUse = true 
exportSession.timeRange =     CMTimeRangeMake(kCMTimeZero, mutableComp.duration) 
exportSession.outputFileType =    AVFileTypeQuickTimeMovie 



exportSession.exportAsynchronously { 
print("finished: \(filePath) : \(exportSession.status.rawValue) ") 

if exportSession.status.rawValue == 4 { 

print("Export failed -> Reason: \(exportSession.error!.localizedDescription))") 
print(exportSession.error!) }}}} 

Dann laufe ich alle drei Methoden in den viewDidLoad Verfahren für einen schnellen Test. Das Problem ist, dass wenn ich die App starte, das Ergebnis des Exports das Originalvideo ohne den Titel ist. Was fehlt mir hier?

UPDATE

Ich bemerke, dass in Teil II des Codes eine subTitle1Text.backgroundColor Eigenschaft Hinzufügen von entsprechenden einer farbigen CGRect macht subTitle1Text.frame auf dem Video angezeigt, wenn exportiert. (See Image) Wenn dieser Code für die Wiedergabe mit AVSynchronizedLayer geändert wird, kann die gewünschte Ebene über dem Video mit Text darauf angezeigt werden. Also vielleicht ist das ein Fehler in AVFoundation selbst.

Ich nehme an, ich bin nur mit der Option der Verwendung eines customVideoCompositorClass verlassen. Das Problem dabei ist, dass das Rendern des Videos sehr lange dauert. Hier ist ein Beispiel, das AVVideoCompositing verwendet:

Antwort

1

Hier ist voll funktionsfähige Code, den ich in meinem Projekt verwendet. Es wird CATextLayer unten (0,0) angezeigt. Und in der Exportsitzung wird der neue Pfad im Spielerelement ersetzt. Ich habe ein Modell von Objective C Code verwendet, um Orientierung zu bekommen. Bitte testen Sie im Gerät. AVPlayer zeigt die Textebene im Simulator nicht korrekt an.

let composition = AVMutableComposition.init() 

    let videoComposition = AVMutableVideoComposition() 
    videoComposition.frameDuration = CMTimeMake(1, 30) 
    videoComposition.renderScale = 1.0 

    let compositionCommentaryTrack: AVMutableCompositionTrack? = composition.addMutableTrack(withMediaType: AVMediaTypeAudio, preferredTrackID: kCMPersistentTrackID_Invalid) 


    let compositionVideoTrack: AVMutableCompositionTrack? = composition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID: kCMPersistentTrackID_Invalid) 


    let clipVideoTrack:AVAssetTrack = self.currentAsset.tracks(withMediaType: AVMediaTypeVideo)[0] 

    let audioTrack: AVAssetTrack? = self.currentAsset.tracks(withMediaType: AVMediaTypeAudio)[0] 

    try? compositionCommentaryTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, self.currentAsset.duration), of: audioTrack!, at: kCMTimeZero) 

    try? compositionVideoTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, self.currentAsset.duration), of: clipVideoTrack, at: kCMTimeZero) 

    let orientation = VideoModel.videoOrientation(self.currentAsset) 
    var isPortrait = false 

    switch orientation { 
    case .landscapeRight: 
     isPortrait = false 
    case .landscapeLeft: 
     isPortrait = false 
    case .portrait: 
     isPortrait = true 
    case .portraitUpsideDown: 
     isPortrait = true 
    } 

    var naturalSize = clipVideoTrack.naturalSize 

    if isPortrait 
    { 
     naturalSize = CGSize.init(width: naturalSize.height, height: naturalSize.width) 
    } 

    videoComposition.renderSize = naturalSize 

    let scale = CGFloat(1.0) 

    var transform = CGAffineTransform.init(scaleX: CGFloat(scale), y: CGFloat(scale)) 

    switch orientation { 
    case .landscapeRight: break 
    // isPortrait = false 
    case .landscapeLeft: 
     transform = transform.translatedBy(x: naturalSize.width, y: naturalSize.height) 
     transform = transform.rotated(by: .pi) 
    case .portrait: 
     transform = transform.translatedBy(x: naturalSize.width, y: 0) 
     transform = transform.rotated(by: CGFloat(M_PI_2)) 
    case .portraitUpsideDown:break 
    } 

    let frontLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionVideoTrack!) 
    frontLayerInstruction.setTransform(transform, at: kCMTimeZero) 

    let MainInstruction = AVMutableVideoCompositionInstruction() 
    MainInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, composition.duration) 
    MainInstruction.layerInstructions = [frontLayerInstruction] 
    videoComposition.instructions = [MainInstruction] 

    let parentLayer = CALayer.init() 
    parentLayer.frame = CGRect.init(x: 0, y: 0, width: naturalSize.width, height: naturalSize.height) 

    let videoLayer = CALayer.init() 
    videoLayer.frame = parentLayer.frame 


    let layer = CATextLayer() 
    layer.string = "HELLO ALL" 
    layer.foregroundColor = UIColor.white.cgColor 
    layer.backgroundColor = UIColor.orange.cgColor 
    layer.fontSize = 32 
    layer.frame = CGRect.init(x: 0, y: 0, width: 300, height: 100) 

    var rct = layer.frame; 

    let widthScale = self.playerView.frame.size.width/naturalSize.width 

    rct.size.width /= widthScale 
    rct.size.height /= widthScale 
    rct.origin.x /= widthScale 
    rct.origin.y /= widthScale 



    parentLayer.addSublayer(videoLayer) 
    parentLayer.addSublayer(layer) 

    videoComposition.animationTool = AVVideoCompositionCoreAnimationTool.init(postProcessingAsVideoLayer: videoLayer, in: parentLayer) 

    let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] 
    let videoPath = documentsPath+"/cropEditVideo.mov" 

    let fileManager = FileManager.default 

    if fileManager.fileExists(atPath: videoPath) 
    { 
     try! fileManager.removeItem(atPath: videoPath) 
    } 

    print("video path \(videoPath)") 

    var exportSession = AVAssetExportSession.init(asset: composition, presetName: AVAssetExportPresetHighestQuality) 
    exportSession?.videoComposition = videoComposition 
    exportSession?.outputFileType = AVFileTypeQuickTimeMovie 
    exportSession?.outputURL = URL.init(fileURLWithPath: videoPath) 
    exportSession?.videoComposition = videoComposition 
    var exportProgress: Float = 0 
    let queue = DispatchQueue(label: "Export Progress Queue") 
    queue.async(execute: {() -> Void in 
     while exportSession != nil { 
      //    int prevProgress = exportProgress; 
      exportProgress = (exportSession?.progress)! 
      print("current progress == \(exportProgress)") 
      sleep(1) 
     } 
    }) 

    exportSession?.exportAsynchronously(completionHandler: { 


     if exportSession?.status == AVAssetExportSessionStatus.failed 
     { 
      print("Failed \(exportSession?.error)") 
     }else if exportSession?.status == AVAssetExportSessionStatus.completed 
     { 
      exportSession = nil 

      let asset = AVAsset.init(url: URL.init(fileURLWithPath: videoPath)) 
      DispatchQueue.main.async { 
       let item = AVPlayerItem.init(asset: asset) 


       self.player.replaceCurrentItem(with: item) 

       let assetDuration = CMTimeGetSeconds(composition.duration) 
       self.progressSlider.maximumValue = Float(assetDuration) 

       self.syncLayer.removeFromSuperlayer() 
       self.lblIntro.isHidden = true 

       self.player.play() 
       //     let url = URL.init(fileURLWithPath: videoPath) 
       //     let activityVC = UIActivityViewController(activityItems: [url], applicationActivities: []) 
       //     self.present(activityVC, animated: true, completion: nil) 
      } 

     } 
    }) 

Im Folgenden finden Sie Code of My VideoModel Klasse

-(AVCaptureVideoOrientation)videoOrientation:(AVAsset *)asset 
{ 
    AVCaptureVideoOrientation result = 0; 
    NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo]; 
    if([tracks count] > 0) { 
     AVAssetTrack *videoTrack = [tracks objectAtIndex:0]; 
     CGAffineTransform t = videoTrack.preferredTransform; 
     // Portrait 
     if(t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0) 
     { 
      result = AVCaptureVideoOrientationPortrait; 
     } 
     // PortraitUpsideDown 
     if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0) { 

      result = AVCaptureVideoOrientationPortraitUpsideDown; 
     } 
     // LandscapeRight 
     if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0) 
     { 
      result = AVCaptureVideoOrientationLandscapeRight; 
     } 
     // LandscapeLeft 
     if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0) 
     { 
      result = AVCaptureVideoOrientationLandscapeLeft; 
     } 
    } 
    return result; 
} 

Lassen Sie mich wissen, wenn Sie mehr Hilfe brauchen.

+0

Danke für die Antwort Amrit. Ich habe meinen Code mit deiner Antwort aktualisiert, aber unglücklicherweise gibt es immer noch keinen Untertitel auf dem Video. Das Hinzufügen von AVMutableVideoCompositionInstruction scheint das Problem nicht zu lösen. Das war sehr peinlich. – 6994

+0

check out meine aktualisierte Antwort wird es Ihnen helfen, Ihr Problem zu lösen. –

+0

Ich habe meinen Code so geändert, dass er wieder zu Ihrer Antwort passt. Das Video wird im Simulator exportiert, aber ohne Titel im 'CATextLayer'. Nach deiner Empfehlung habe ich versucht, das Projekt auf einem Gerät auszuführen, aber das Video exportiert überhaupt nicht und wartet weiter, bis die Meldung 'aktueller Fortschritt == 0 'kontinuierlich im Debug-Bereich gedruckt wird. Ich konnte auch keinen Weg finden, dieses Problem zu lösen. – 6994