123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- /*
- Copyright (c) 2020 Swift Models Generated from JSON powered by http://www.json4swift.com
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- For support, please feel free to contact me at https://www.linkedin.com/in/syedabsar
- */
- import Foundation
- import AVFoundation
- import ObjectMapper
- import RealmSwift
- class PQEditSectionModel: PQEditBaseModel {
- @objc dynamic var addAutoEffect: Int = 0
- @objc dynamic var sectionDuration: Float64 = 0 {
- didSet {
- BFLog(message: "sectionDuration == \(sectionDuration)")
- }
- }
- @objc dynamic var projectTimelineIn: Float64 = 0
- @objc dynamic var projectTimelineOut: Float64 = 0
- // 段落序号globle 全是0 nomoal 从1开始++ XXXXXX(已经通过TransformOf处理过,代码中使用时都是从0开始就 OK )
- @objc dynamic var sectionIndex: Int = 0 {
- didSet {
- BFLog(message: "sectionIndex is \(sectionIndex)")
- }
- }
- @objc dynamic var sectionText: String = "" {
- didSet {
- BFLog(message: "文字发生了改变")
-
- }
- }
- @objc dynamic var sectionTimeline: PQEditSectionTimelineModel? {
- didSet {}
- }
- @objc dynamic var sectionType: String = "normal"
- // pc
- @objc dynamic var sectionExtData: PQEditSectionExtDataModel?
- // add by ak 业务逻辑层要使用的属性 -----------
- var textIsChane: Bool = false
- // 是否为选中状态 选中状态有三种,0未选中状态 1 设置状态 2,播放状态
- var isSelected: Int = 0
- // 是否为可设置状态
- var enabledSetting: Bool = true
- // 是否可添加段
- var enableAdd: Bool = true
- // 当前段选择的发音人数据
- var selectVoice: PQVoiceModel?
- // 每一段的封面
- var coverImage: UIImage?
- // 声音文件地址
- var audioFilePath: String = "" {
- didSet {
- if (sectionTimeline?.visionTrack?.getEnableVisionTrackMaterials().count ?? 0) > 0 {
- sectionDuration = allStickerAptDuration()
- } else {
- if audioFilePath.count > 0 {
- let audioAsset = AVURLAsset(url: URL(fileURLWithPath: documensDirectory + audioFilePath), options: nil)
- sectionDuration = Float64(audioAsset.duration.seconds)
- }
- }
- }
- }
- // 谁长用谁功能,如果视觉长会拼接空音频文件,这个是拼接后的地址
- var mixEmptyAuidoFilePath: String = ""
- var subTitles = List<PQEditSubTitleModel>()
- // 背景音乐数据(可能每一段都会有背景音)
- public var bgmData: PQVoiceModel?
- // 录音人头像,在恢复项目时会有值
- var audioAvatarUrl: String = ""
- // MARK: - 录音相关属性,不保存sadata 不入库
- // 保存每一个段落所有分段的录音记录
- var cacheRecorderFiles: [URL] = Array()
- // 录制的小段数
- var cacheRecorderCount: Int = 0
- // 合并后的地址 并已经转成 MP3 mp3 file path
- var compliteMP3AudioFile: String = ""
- // 录音声音分贝值
- var audioPowers: [Int] = Array()
-
- //是否正在文字转换中。。
- var isConverding: Bool = false
- required convenience init?(map _: Map) {
- self.init()
- }
- required init() {
- super.init()
- sectionTimeline = PQEditSectionTimelineModel()
- }
- override func mapping(map: Map) {
- addAutoEffect <- map["addAutoEffect"]
- sectionDuration <- (map["duration"], timeTransform)
- projectTimelineIn <- (map["projectTimelineIn"], timeTransform)
- projectTimelineOut <- (map["projectTimelineOut"], timeTransform)
- sectionText <- map["sectionText"]
- sectionTimeline <- map["sectionTimeline"]
- sectionType <- map["sectionType"]
- if sectionType != "global" {
- sectionIndex <- (map["sectionIndex"], sectionIndexTransform)
- } else {
- sectionIndex <- map["sectionIndex"]
- }
- sectionExtData <- map["sectionExtData"]
- // 业务逻辑属性
- audioFilePath <- map["audioFilePath"]
- mixEmptyAuidoFilePath <- map["mixEmptyAuidoFilePath"]
- subTitles <- (map["subTitles"], PQListTransform<PQEditSubTitleModel>())
- uniqueId <- map["uniqueId"]
- }
- // 计算所有贴纸的时间 aptDuration 已经是根据有无配音计算好的 可直接使用
- func allStickerAptDuration() -> Float64 {
- var stickerAptDuration: Float64 = 0
- if sectionTimeline?.visionTrack?.getEnableVisionTrackMaterials().count ?? 0 > 0 {
- for sticker in (sectionTimeline!.visionTrack?.getEnableVisionTrackMaterials())! {
- stickerAptDuration = stickerAptDuration + Double(lround(sticker.aptDuration))
- }
- }
- return stickerAptDuration
- }
- // 所有内贴纸的时长未4舍5入的
- func allStickerAptDurationNoRound() -> Float64 {
- var stickerAptDuration: Float64 = 0
- if sectionTimeline?.visionTrack?.getEnableVisionTrackMaterials().count ?? 0 > 0 {
- for sticker in (sectionTimeline!.visionTrack?.getEnableVisionTrackMaterials())! {
- stickerAptDuration = stickerAptDuration + Double(sticker.aptDuration)
- }
- }
- return stickerAptDuration
- }
- /// 是否已经设置过一个发音人,voice 会在选择时给值,取消选择时清空 init
- func haveSelectVoice() -> Bool {
- BFLog(message: "voice?.avatarUrl \(String(describing: selectVoice?.avatarUrl))")
- return selectVoice?.avatarUrl.count ?? 0 > 0
- }
- /// 判断素材是否下载完成,如果本地址没有,就应该是有问题或没有下载完成
- /// - Returns: <#description#>
- func matrialIsDownloaded() -> Bool {
- var isDownloaded: Bool = true // 素材是否下载完成
- for sticker in (sectionTimeline!.visionTrack?.getEnableVisionTrackMaterials())! {
- if sticker.locationPath.count == 0 {
- isDownloaded = false
- break
- }
- }
- return isDownloaded
- }
- // 添加字幕信息
- func addSubtitleMatraislInfo(subtitls: List<PQEditVisionTrackMaterialsModel>) {
- deleteSubtitleMatraislInfo()
- // 2,添加新的一组字幕
- sectionTimeline!.visionTrack?.visionTrackMaterials.append(objectsIn: subtitls)
- }
- // 删除字幕
- func deleteSubtitleMatraislInfo() {
- // 1,确保每一个段落只有一组字幕 先移除老的一组字幕如果有, 只设置有效的素材
- let otherEnableVision = sectionTimeline!.visionTrack?.getEnableVisionTrackMaterials()
- sectionTimeline!.visionTrack?.visionTrackMaterials = otherEnableVision ?? List<PQEditVisionTrackMaterialsModel>.init()
- }
- // 判断当前段落是否有有效素材,1, 有视觉素材,2,有声音数据1)录音2)配音-- audioFilePath (如果是空的声音不算) 3,字幕 1)输入的的 2)录音转的
- func haveRes() -> Bool {
- var have: Bool = false
- if subTitles.count > 0 || (sectionTimeline?.visionTrack?.getEnableVisionTrackMaterials().count ?? 0) > 0 || (audioFilePath.count > 0 && !audioFilePath.contains("empty")) || sectionTimeline?.visionTrack?.getSubtitleMatraislInfo().count ?? 0 > 0 || sectionText.count > 0 {
- have = true
- }
- return have
- }
- /* 有一个特殊场景 根据当前段的视觉素材生成空声音文件
- 1)所有段没有文字
- 2)p1有视觉 p2 有
- 3)p1没有预览 ,P2直接点了全览
- 这个情况 P1 还没有生成空的文件会导致所有时长不对,贴纸在选择加入的时候已经处理,这里只处理这种 PART 的空声音文件
- */
- func generateEmptyAuido() {
- var stickerTotalDuration: Float64 = 0
- // 没有选择发声音人,只有纯视觉的情况
- if sectionText.count == 0, audioFilePath.count == 0, mixEmptyAuidoFilePath.count == 0 {
- BFLog(message: "section index is \(sectionIndex)")
- if !isEmptyObject(object: sectionTimeline?.visionTrack?.getEnableVisionTrackMaterials()) {
- for sticker in sectionTimeline!.visionTrack!.getEnableVisionTrackMaterials() {
- if sticker.timelineOut == 0, sticker.timelineIn == 0 {
- stickerTotalDuration = stickerTotalDuration + (sticker.out - sticker.model_in)
- } else {
- stickerTotalDuration = stickerTotalDuration + (sticker.timelineOut - sticker.timelineIn)
- }
- }
- }
- // 创建空的数据
- let tool = PQCreateEmptyWAV(sampleRate: 44100,
- channel: 1,
- duration: stickerTotalDuration,
- bit: 16)
- let timeInterval: TimeInterval = Date().timeIntervalSince1970
- var documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first! as String
- documentPath.append("/\(timeInterval).wav")
- tool.createEmptyWAVFile(url: URL(fileURLWithPath: documentPath))
-
- audioFilePath = documentPath
- sectionDuration = CMTimeGetSeconds(AVURLAsset(url: URL(fileURLWithPath: documentPath), options: nil).duration)
- BFLog(message: " 生成的空声音 \(String(describing: documentPath)) 时长 \(String(describing: sectionDuration))")
-
- }
- }
- // 生成输入状态的可使用的文字信息
- func getInputSubtitle() -> String {
- var text: String = ""
- if sectionTimeline?.visionTrack?.getSubtitleMatraislInfo() != nil {
- for subTitleModel in sectionTimeline!.visionTrack!.getSubtitleMatraislInfo() {
- if subTitleModel.subtitleInfo?.text.count ?? 0 > 0 {
- text = text + (subTitleModel.subtitleInfo?.text ?? "") + "\n"
- }
- }
- }
- return text
- }
- /// 删除录音的缓存 文件,在删除段落时调用
- func deleteCacheAudioFiles(isDeleteMergeFile: Bool = false) {
- let fileManger = FileManager.default
- // 删除临时音频文件数据
- // file:///var/mobile/Containers/Data/Application/176BE83D-F514-49EA-8710-8A077CFCFA72/Documents/Resource/ExportAudios/recorder_1614170151.815164_noise.wav
- for url in cacheRecorderFiles {
- if fileManger.fileExists(atPath: url.relativePath) {
- do {
- try fileManger.removeItem(atPath: url.relativePath)
- print("\(url.relativePath)Success to remove file.")
- } catch {
- print("Failed to remove file.")
- }
- }
- }
- cacheRecorderFiles.removeAll()
- // 2 删除合并后的文件
- if isDeleteMergeFile {
- if fileManger.fileExists(atPath: compliteMP3AudioFile) {
- do {
- try fileManger.removeItem(atPath: compliteMP3AudioFile)
- print("\(compliteMP3AudioFile)Success to remove file.")
- } catch {
- print("Failed to remove file.")
- }
- }
- }
- compliteMP3AudioFile = ""
- audioPowers = []
- }
- }
|