|
@@ -76,6 +76,12 @@ class PQStuckPointPublicController: PQBaseViewController {
|
|
// 导出片尾
|
|
// 导出片尾
|
|
private var endMovieExporter: PQCompositionExporter!
|
|
private var endMovieExporter: PQCompositionExporter!
|
|
private var endMovieLocalURL: URL? // 导出的地址
|
|
private var endMovieLocalURL: URL? // 导出的地址
|
|
|
|
+ // 保存相册的合成视频地址 水印+片尾
|
|
|
|
+ private var saveMovieLocalURL: URL?
|
|
|
|
+ //用户名绘制用到的参数
|
|
|
|
+ private var userNameTop:Int = 0
|
|
|
|
+ private var userNameFontSize:Int = 0
|
|
|
|
+ private var userNameSize: CGRect = .zero
|
|
//----------------------------
|
|
//----------------------------
|
|
|
|
|
|
// 预览大小
|
|
// 预览大小
|
|
@@ -580,6 +586,16 @@ class PQStuckPointPublicController: PQBaseViewController {
|
|
|
|
|
|
// 取推荐标题
|
|
// 取推荐标题
|
|
getTitles()
|
|
getTitles()
|
|
|
|
+
|
|
|
|
+ //片尾用户名到顶部的高度
|
|
|
|
+ userNameTop = Int(870 * (editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) / 1920)
|
|
|
|
+ userNameFontSize = Int(100 * (editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) / 1080)
|
|
|
|
+ if((editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) <= (editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0)){
|
|
|
|
+ userNameTop = 480
|
|
|
|
+ userNameFontSize = 70
|
|
|
|
+ }
|
|
|
|
+ //片尾用户名绘制大小
|
|
|
|
+ userNameSize = BFLoginUserInfo.shared.nickName.boundingRect(with: CGSize(width: userNameFontSize * 12, height: 0), options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: CGFloat(100 * (editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) / 1080)), NSAttributedString.Key.kern: 0 ], context: nil)
|
|
}
|
|
}
|
|
|
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
@@ -873,7 +889,7 @@ extension PQStuckPointPublicController {
|
|
/// - Returns: <#description#>
|
|
/// - Returns: <#description#>
|
|
func saveStuckPointVideo() {
|
|
func saveStuckPointVideo() {
|
|
|
|
|
|
- if(watermarkMovieLocalURL == nil){
|
|
|
|
|
|
+ if(saveMovieLocalURL == nil){
|
|
BFLog(message: "保存相册的视频导出地址无效!!!")
|
|
BFLog(message: "保存相册的视频导出地址无效!!!")
|
|
return
|
|
return
|
|
}
|
|
}
|
|
@@ -881,7 +897,7 @@ extension PQStuckPointPublicController {
|
|
if authStatus == .authorized {
|
|
if authStatus == .authorized {
|
|
let photoLibrary = PHPhotoLibrary.shared()
|
|
let photoLibrary = PHPhotoLibrary.shared()
|
|
photoLibrary.performChanges({ [weak self] in
|
|
photoLibrary.performChanges({ [weak self] in
|
|
- PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: (self?.watermarkMovieLocalURL)!)
|
|
|
|
|
|
+ PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: (self?.saveMovieLocalURL)!)
|
|
}) { [weak self] isFinished, _ in
|
|
}) { [weak self] isFinished, _ in
|
|
DispatchQueue.main.async { [weak self] in
|
|
DispatchQueue.main.async { [weak self] in
|
|
if self?.view != nil {
|
|
if self?.view != nil {
|
|
@@ -1211,8 +1227,9 @@ extension PQStuckPointPublicController {
|
|
cShowHUB(superView: nil, msg: "视频发布失败,请重新合成")
|
|
cShowHUB(superView: nil, msg: "视频发布失败,请重新合成")
|
|
} else {
|
|
} else {
|
|
bottomOprationBgView.isHidden = false
|
|
bottomOprationBgView.isHidden = false
|
|
- /// fp2-1-1 - 请求权限
|
|
|
|
- authorizationStatus()
|
|
|
|
|
|
+
|
|
|
|
+ //开始导出片尾 成功后自动保存到相册
|
|
|
|
+ beginExportEndMovie()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -1590,9 +1607,7 @@ extension PQStuckPointPublicController {
|
|
}
|
|
}
|
|
|
|
|
|
self?.watermarkMovieLocalURL = url
|
|
self?.watermarkMovieLocalURL = url
|
|
-
|
|
|
|
- //开始导出片尾
|
|
|
|
- self?.beginExportEndMovie()
|
|
|
|
|
|
+
|
|
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -1615,7 +1630,7 @@ extension PQStuckPointPublicController {
|
|
var orgeBitRate = (editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) * (editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) * 3
|
|
var orgeBitRate = (editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) * (editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) * 3
|
|
|
|
|
|
//片尾的视频素材地址
|
|
//片尾的视频素材地址
|
|
- let moveResPath = Bundle().BF_mainbundle().path(forResource: "endMovieA", ofType: "mp4")
|
|
|
|
|
|
+ let moveResPath = Bundle().BF_mainbundle().path(forResource: (editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) < (editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) ? "endMovieB" : "endMovieA", ofType: "mp4")
|
|
if(moveResPath?.count ?? 0 == 0){
|
|
if(moveResPath?.count ?? 0 == 0){
|
|
BFLog(message: "片尾的视频素材地址无效!!!")
|
|
BFLog(message: "片尾的视频素材地址无效!!!")
|
|
return
|
|
return
|
|
@@ -1628,41 +1643,116 @@ extension PQStuckPointPublicController {
|
|
orgeBitRate = Int(cbr ?? 0)
|
|
orgeBitRate = Int(cbr ?? 0)
|
|
}
|
|
}
|
|
BFLog(message: "导出设置的码率为:\(orgeBitRate)")
|
|
BFLog(message: "导出设置的码率为:\(orgeBitRate)")
|
|
- //视频素材
|
|
|
|
- let stickerInfo:PQEditVisionTrackMaterialsModel = PQEditVisionTrackMaterialsModel.init()
|
|
|
|
- stickerInfo.type = StickerType.VIDEO.rawValue
|
|
|
|
- stickerInfo.locationPath = moveResPath ?? ""
|
|
|
|
- stickerInfo.timelineIn = 0
|
|
|
|
- stickerInfo.timelineOut = CMTimeGetSeconds(movieAsset.duration)
|
|
|
|
- stickerInfo.model_in = stickerInfo.timelineIn
|
|
|
|
- stickerInfo.out = stickerInfo.timelineOut
|
|
|
|
- stickerInfo.canvasFillType = stickerContentMode.aspectFitStr.rawValue
|
|
|
|
-
|
|
|
|
- let soundResPath = Bundle().BF_mainbundle().path(forResource: "endMovieSound", ofType: "mp3")
|
|
|
|
-
|
|
|
|
- let soundAsset = AVURLAsset(url: URL(fileURLWithPath: soundResPath ?? ""), options: nil)
|
|
|
|
- endMovieExporter = PQCompositionExporter(asset: soundAsset, videoComposition: nil, audioMix: nil, filters: nil, stickers: [stickerInfo], animationTool: nil, exportURL: outPutMP4URL)
|
|
|
|
|
|
+
|
|
|
|
+ //头像保存沙盒地址
|
|
|
|
+ BFLog(message: "头像的网络地址\(BFLoginUserInfo.shared.avatarUrl)")
|
|
|
|
+ let avatarFilePath = NSHomeDirectory().appending("/Documents/").appending("user_avatar.jpg")
|
|
|
|
+ coverImageView.kf.setImage(with: URL(string: BFLoginUserInfo.shared.avatarUrl), placeholder: UIImage().BF_Image(named: "user_avatar_normal"), progressBlock: { _, _ in
|
|
|
|
+
|
|
|
|
+ }) { [weak self] result in
|
|
|
|
+ switch result {
|
|
|
|
+ case let .failure(failure):
|
|
|
|
+ BFLog(message: "图片请求失败:\(failure.localizedDescription)")
|
|
|
|
+ case let .success(imageResult):
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ let image = UIImage.nx_circleImage(imageResult.image)
|
|
|
|
+ if(image == nil){
|
|
|
|
+ BFLog(message: "image date is error!!")
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ UIImage.saveImage(currentImage: image!, outFilePath: avatarFilePath)
|
|
|
|
+
|
|
|
|
+ //1,背景视频素材
|
|
|
|
+ let bgMovieInfo:PQEditVisionTrackMaterialsModel = PQEditVisionTrackMaterialsModel.init()
|
|
|
|
+ bgMovieInfo.type = StickerType.VIDEO.rawValue
|
|
|
|
+ bgMovieInfo.locationPath = moveResPath ?? ""
|
|
|
|
+ bgMovieInfo.timelineIn = 0
|
|
|
|
+ bgMovieInfo.timelineOut = CMTimeGetSeconds(movieAsset.duration)
|
|
|
|
+ bgMovieInfo.model_in = bgMovieInfo.timelineIn
|
|
|
|
+ bgMovieInfo.out = bgMovieInfo.timelineOut
|
|
|
|
+ bgMovieInfo.canvasFillType = stickerContentMode.aspectFitStr.rawValue
|
|
|
|
+ //2,用户头像素材
|
|
|
|
+ BFLog(message: "头像的沙盒地址:\(avatarFilePath)")
|
|
|
|
+ let avatarSticker:PQEditVisionTrackMaterialsModel = PQEditVisionTrackMaterialsModel.init()
|
|
|
|
+ avatarSticker.locationPath = avatarFilePath.replacingOccurrences(of: documensDirectory, with: "")
|
|
|
|
+ avatarSticker.timelineIn = bgMovieInfo.timelineIn
|
|
|
|
+ avatarSticker.timelineOut = bgMovieInfo.timelineOut
|
|
|
|
+
|
|
|
|
+ //头像绘制大小
|
|
|
|
+ var avatarSize = Int(360 * (self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) / 1080)
|
|
|
|
+ //头像到顶部的高度
|
|
|
|
+ var avatarTop = Int(430 * (self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) / 1920)
|
|
|
|
+ if((self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0) <= (self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0)){//横屏
|
|
|
|
+ avatarSize = 300
|
|
|
|
+ avatarTop = 130
|
|
|
|
+ }
|
|
|
|
+ let avatarPostion:PQEditMaterialPositionModel = PQEditMaterialPositionModel.init()
|
|
|
|
+ avatarPostion.width = avatarSize
|
|
|
|
+ avatarPostion.height = avatarSize
|
|
|
|
+ avatarPostion.x = ((self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) - avatarSize) / 2
|
|
|
|
+ avatarPostion.y = avatarTop
|
|
|
|
+ avatarSticker.materialPosition = avatarPostion
|
|
|
|
+
|
|
|
|
+ //3,用户名素材
|
|
|
|
+ let userNameSticker:PQEditVisionTrackMaterialsModel = PQEditVisionTrackMaterialsModel.init()
|
|
|
|
+ userNameSticker.timelineIn = bgMovieInfo.timelineIn
|
|
|
|
+ userNameSticker.timelineOut = bgMovieInfo.timelineOut
|
|
|
|
+ userNameSticker.type = StickerType.SUBTITLE.rawValue
|
|
|
|
+ let subtitleInfo:PQEditSubtitleInfoModel = PQEditSubtitleInfoModel.init()
|
|
|
|
+ subtitleInfo.fontSize = self?.userNameFontSize ?? 0
|
|
|
|
+ subtitleInfo.text = BFLoginUserInfo.shared.nickName
|
|
|
|
+ userNameSticker.subtitleInfo = subtitleInfo
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ let userNamePostion:PQEditMaterialPositionModel = PQEditMaterialPositionModel.init()
|
|
|
|
+ userNamePostion.width = Int(self?.userNameSize.width ?? 0)
|
|
|
|
+ userNamePostion.height = Int(self?.userNameSize.height ?? 0)
|
|
|
|
+ userNamePostion.x = ((self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0) - Int(self?.userNameSize.width ?? 0)) / 2
|
|
|
|
+ userNamePostion.y = self?.userNameTop ?? 0
|
|
|
|
+ userNameSticker.materialPosition = userNamePostion
|
|
|
|
+
|
|
|
|
+ //4,音频
|
|
|
|
+ let soundResPath = Bundle().BF_mainbundle().path(forResource: "endMovieSound", ofType: "mp3")
|
|
|
|
+ let soundAsset = AVURLAsset(url: URL(fileURLWithPath: soundResPath ?? ""), options: nil)
|
|
|
|
+ self?.endMovieExporter = PQCompositionExporter(asset: soundAsset, videoComposition: nil, audioMix: nil, filters: nil, stickers: [bgMovieInfo,avatarSticker,userNameSticker], animationTool: nil, exportURL: outPutMP4URL)
|
|
|
|
+ self?.endMovieExporter.isEndMovie = true
|
|
|
|
+ if self?.endMovieExporter.prepare(videoSize: CGSize(width: self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0, height: self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0), videoAverageBitRate: orgeBitRate) ?? false {
|
|
|
|
+
|
|
|
|
+ self?.endMovieExporter.start(playeTimeRange: CMTimeRange.init(start: CMTime.zero, duration: CMTimeMakeWithSeconds(Float64(bgMovieInfo.out), preferredTimescale: BASE_FILTER_TIMESCALE)))
|
|
|
|
+ BFLog(message: "开始导出")
|
|
|
|
+ }
|
|
|
|
+ self?.endMovieExporter.progressClosure = { [weak self] _, _, progress in
|
|
|
|
+ BFLog(message: "片尾合成进度 \(progress) ")
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+ self?.endMovieExporter.completion = { [weak self] url in
|
|
|
|
+ BFLog(message: "片尾的视频导出完成: \(url) 生成视频时长为:\(CMTimeGetSeconds(AVAsset(url: url).duration))")
|
|
|
|
|
|
- endMovieExporter.showGaussianBlur = true
|
|
|
|
- if endMovieExporter.prepare(videoSize: CGSize(width: editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0, height: editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0), videoAverageBitRate: orgeBitRate) {
|
|
|
|
-
|
|
|
|
- endMovieExporter.start(playeTimeRange: CMTimeRange.init(start: CMTime.zero, duration: CMTimeMakeWithSeconds(Float64(stickerInfo.out), preferredTimescale: BASE_FILTER_TIMESCALE)))
|
|
|
|
- BFLog(message: "开始导出")
|
|
|
|
- }
|
|
|
|
- endMovieExporter.progressClosure = { [weak self] _, _, progress in
|
|
|
|
- BFLog(message: "片尾合成进度 \(progress) ")
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
- endMovieExporter.completion = { [weak self] url in
|
|
|
|
- BFLog(message: "片尾的视频导出完成: \(url) 生成视频时长为:\(CMTimeGetSeconds(AVAsset(url: url).duration))")
|
|
|
|
|
|
+ // 导出完成后取消导出
|
|
|
|
+ if self?.endMovieExporter != nil {
|
|
|
|
+ self?.endMovieExporter.cancel()
|
|
|
|
+ }
|
|
|
|
+ self?.endMovieLocalURL = url
|
|
|
|
+ //拼接水印正片和片尾
|
|
|
|
+ if(self?.watermarkMovieLocalURL != nil && self?.endMovieLocalURL != nil){
|
|
|
|
+ let videoMerge:NXVideoMerge = NXVideoMerge.init()
|
|
|
|
+ videoMerge.mergeAndExportVideos(withFileURLs: [self!.watermarkMovieLocalURL!,self!.endMovieLocalURL!], renderSize:CGSize(width: self?.editProjectModel?.sData?.videoMetaData?.videoWidth ?? 0, height: self?.editProjectModel?.sData?.videoMetaData?.videoHeight ?? 0)) { isSuccess, outFileURL in
|
|
|
|
+ if(isSuccess){
|
|
|
|
+ BFLog(message: "合并视频成功 outFilePath is \(outFileURL ?? "")")
|
|
|
|
+ self?.saveMovieLocalURL = outFileURL as? URL
|
|
|
|
+ //保存到相册 fp2-1-1 - 请求权限
|
|
|
|
+ self?.authorizationStatus()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
|
|
- // 导出完成后取消导出
|
|
|
|
- if self?.endMovieExporter != nil {
|
|
|
|
- self?.endMovieExporter.cancel()
|
|
|
|
}
|
|
}
|
|
- self?.endMovieLocalURL = url
|
|
|
|
-
|
|
|
|
}
|
|
}
|
|
|
|
+
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|