Procházet zdrojové kódy

Merge branch '3.3.2' into 3.3

# Conflicts:
#	AIEmoji/Business/TSAILIstVC/TSAIPhotoGeneratorBaseVC/TSAIPhotoGeneratorBaseVM/TSAIListPhotoGeneratorBaseVM.swift
100Years před 2 dny
rodič
revize
488944eb31
34 změnil soubory, kde provedl 1121 přidání a 361 odebrání
  1. 28 8
      AIEmoji.xcodeproj/project.pbxproj
  2. 22 0
      AIEmoji/Assets.xcassets/AIList/aiList_livePhoto.imageset/Contents.json
  3. binární
      AIEmoji/Assets.xcassets/AIList/aiList_livePhoto.imageset/aiList_livePhoto@2x.png
  4. binární
      AIEmoji/Assets.xcassets/AIList/aiList_livePhoto.imageset/aiList_livePhoto@3x.png
  5. 22 0
      AIEmoji/Assets.xcassets/Common/pause.imageset/Contents.json
  6. binární
      AIEmoji/Assets.xcassets/Common/pause.imageset/pause@2x.png
  7. binární
      AIEmoji/Assets.xcassets/Common/pause.imageset/pause@3x.png
  8. 22 0
      AIEmoji/Assets.xcassets/Common/play.imageset/Contents.json
  9. binární
      AIEmoji/Assets.xcassets/Common/play.imageset/play@2x.png
  10. binární
      AIEmoji/Assets.xcassets/Common/play.imageset/play@3x.png
  11. 22 0
      AIEmoji/Assets.xcassets/Common/video_icon.imageset/Contents.json
  12. binární
      AIEmoji/Assets.xcassets/Common/video_icon.imageset/video_icon@2x.png
  13. binární
      AIEmoji/Assets.xcassets/Common/video_icon.imageset/video_icon@3x.png
  14. 12 4
      AIEmoji/Business/Data/TSUserDefaultData.swift
  15. 18 0
      AIEmoji/Business/TSAILIstVC/TSAILIstVC/TSAILIstVC.swift
  16. 91 0
      AIEmoji/Business/TSAILIstVC/TSAIListHistoryBaseVC/TSAIListHistoryBaseCell.swift
  17. 4 0
      AIEmoji/Business/TSAILIstVC/TSAIListHistoryBaseVC/TSAIListHistoryBaseVM.swift
  18. 272 0
      AIEmoji/Business/TSAILIstVC/TSAIListVideoPlayerVC/TSAIListVideoPlayerVC.swift
  19. 54 8
      AIEmoji/Business/TSAILIstVC/TSAIPhotoGeneratorBaseVC/TSAIListPhotoGeneratorBaseVC.swift
  20. 71 22
      AIEmoji/Business/TSAILIstVC/TSAIPhotoGeneratorBaseVC/TSAIPhotoGeneratorBaseVM/TSAIListPhotoGeneratorBaseVM.swift
  21. 3 0
      AIEmoji/Business/TSAILIstVC/TSAIUploadPhotoBaseVC/TSAIUploadPhotoBaseVC.swift
  22. 18 1
      AIEmoji/Business/TSGenmojiVC/TSGenmojiVC/Model/TSGenmojiModel.swift
  23. 1 1
      AIEmoji/Business/TSGenmojiVC/TSGenmojiVC/View/TSGenmojiItemCell.swift
  24. 4 1
      AIEmoji/Business/TSGenmojiVC/TSGenmojiVC/ViewModel/TSGenmojiCollectionViewModel.swift
  25. 75 10
      AIEmoji/Business/TSPTPGeneratorVC/TSAIPhotoGeneratorBaseVC/TSAIPhotoBrowseVC.swift
  26. 3 2
      AIEmoji/Business/TSPTPGeneratorVC/TSAIPhotoGeneratorBaseVC/TSAIPhotoGeneratorBaseVC.swift
  27. 0 144
      AIEmoji/Common/GlobalImports/GlobalImports.swift
  28. 28 0
      AIEmoji/Common/NetworkManager/TSNetWork/TSNetWork+Business.swift
  29. 65 0
      AIEmoji/Common/NetworkManager/TSNetWork/TSNetworkManager.swift
  30. 3 3
      AIEmoji/Common/Purchase/TSPurchaseManager.swift
  31. 39 0
      AIEmoji/Common/Tool/PhotoManager.swift
  32. 198 0
      AIEmoji/Common/Tool/TSBusinessFileManager.swift
  33. 46 0
      AIEmoji/Common/Tool/TSDownloadManager.swift
  34. 0 157
      AIEmoji/Common/Tool/TSFileManagerTool.swift

+ 28 - 8
AIEmoji.xcodeproj/project.pbxproj

@@ -165,6 +165,10 @@
 		A8BA76752DA67E66000B6707 /* TSAIListHistoryBaseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8BA76742DA67E65000B6707 /* TSAIListHistoryBaseVC.swift */; };
 		A8BA76772DA68619000B6707 /* TSAIListHistoryBaseVM.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8BA76762DA68617000B6707 /* TSAIListHistoryBaseVM.swift */; };
 		A8D638352DB10BAC00A96C0E /* CrashReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D638342DB10BAB00A96C0E /* CrashReporter.swift */; };
+		A8D6383C2DB1FC8D00A96C0E /* TSAIListVideoPlayerVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D6383B2DB1FC8A00A96C0E /* TSAIListVideoPlayerVC.swift */; };
+		A8D638472DB21FAD00A96C0E /* TSBusinessFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D638452DB21FAD00A96C0E /* TSBusinessFileManager.swift */; };
+		A8D638482DB21FAD00A96C0E /* TSDownloadManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D638462DB21FAD00A96C0E /* TSDownloadManager.swift */; };
+		A8D6384A2DB252F100A96C0E /* TSAIListHistoryBaseCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8D638492DB252F000A96C0E /* TSAIListHistoryBaseCell.swift */; };
 		A8EEADD42D3E6C660032C5A0 /* Flower💐.json in Resources */ = {isa = PBXBuildFile; fileRef = A8EEADD32D3E6C610032C5A0 /* Flower💐.json */; };
 		A8EEADD62D3E6CD80032C5A0 /* Fish🐠.json in Resources */ = {isa = PBXBuildFile; fileRef = A8EEADD52D3E6CD30032C5A0 /* Fish🐠.json */; };
 		A8EEADD82D3E74D20032C5A0 /* Pink🩷.json in Resources */ = {isa = PBXBuildFile; fileRef = A8EEADD72D3E74CB0032C5A0 /* Pink🩷.json */; };
@@ -187,7 +191,6 @@
 		A8F7748E2D38E8B700AA6E93 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A8F774822D38E8B700AA6E93 /* Assets.xcassets */; };
 		A8F774902D38E8B700AA6E93 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A8F774852D38E8B700AA6E93 /* LaunchScreen.storyboard */; };
 		A8F774E02D38EA8C00AA6E93 /* TSCommonTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F774C72D38EA8C00AA6E93 /* TSCommonTool.swift */; };
-		A8F774E12D38EA8C00AA6E93 /* TSFileManagerTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F774CA2D38EA8C00AA6E93 /* TSFileManagerTool.swift */; };
 		A8F775002D38EA8C00AA6E93 /* WindowHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F774CD2D38EA8C00AA6E93 /* WindowHelper.swift */; };
 		A8F775032D38EA8C00AA6E93 /* GlobalImports.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F774B42D38EA8C00AA6E93 /* GlobalImports.swift */; };
 		A8F7750A2D38EA8C00AA6E93 /* TSNetworkTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8F774CB2D38EA8C00AA6E93 /* TSNetworkTool.swift */; };
@@ -416,6 +419,10 @@
 		A8BA76742DA67E65000B6707 /* TSAIListHistoryBaseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIListHistoryBaseVC.swift; sourceTree = "<group>"; };
 		A8BA76762DA68617000B6707 /* TSAIListHistoryBaseVM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIListHistoryBaseVM.swift; sourceTree = "<group>"; };
 		A8D638342DB10BAB00A96C0E /* CrashReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashReporter.swift; sourceTree = "<group>"; };
+		A8D6383B2DB1FC8A00A96C0E /* TSAIListVideoPlayerVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIListVideoPlayerVC.swift; sourceTree = "<group>"; };
+		A8D638452DB21FAD00A96C0E /* TSBusinessFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSBusinessFileManager.swift; sourceTree = "<group>"; };
+		A8D638462DB21FAD00A96C0E /* TSDownloadManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSDownloadManager.swift; sourceTree = "<group>"; };
+		A8D638492DB252F000A96C0E /* TSAIListHistoryBaseCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSAIListHistoryBaseCell.swift; sourceTree = "<group>"; };
 		A8EEADD32D3E6C610032C5A0 /* Flower💐.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "Flower💐.json"; sourceTree = "<group>"; };
 		A8EEADD52D3E6CD30032C5A0 /* Fish🐠.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "Fish🐠.json"; sourceTree = "<group>"; };
 		A8EEADD72D3E74CB0032C5A0 /* Pink🩷.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = "Pink🩷.json"; sourceTree = "<group>"; };
@@ -441,7 +448,6 @@
 		A8F774842D38E8B700AA6E93 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
 		A8F774B42D38EA8C00AA6E93 /* GlobalImports.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalImports.swift; sourceTree = "<group>"; };
 		A8F774C72D38EA8C00AA6E93 /* TSCommonTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSCommonTool.swift; sourceTree = "<group>"; };
-		A8F774CA2D38EA8C00AA6E93 /* TSFileManagerTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSFileManagerTool.swift; sourceTree = "<group>"; };
 		A8F774CB2D38EA8C00AA6E93 /* TSNetworkTool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSNetworkTool.swift; sourceTree = "<group>"; };
 		A8F774CD2D38EA8C00AA6E93 /* WindowHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowHelper.swift; sourceTree = "<group>"; };
 		A8F775162D38EB7400AA6E93 /* TSTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSTabBarController.swift; sourceTree = "<group>"; };
@@ -1332,6 +1338,7 @@
 		A8BA765F2DA6479A000B6707 /* TSAILIstVC */ = {
 			isa = PBXGroup;
 			children = (
+				A8D6383A2DB1FC6E00A96C0E /* TSAIListVideoPlayerVC */,
 				A8F413562DA8E500001E715A /* TSAIChangeEmoteVC */,
 				A8BA76732DA67E60000B6707 /* TSAIListHistoryBaseVC */,
 				A8BA76702DA65A81000B6707 /* TSAIUploadPhotoBaseVC */,
@@ -1387,12 +1394,21 @@
 		A8BA76732DA67E60000B6707 /* TSAIListHistoryBaseVC */ = {
 			isa = PBXGroup;
 			children = (
+				A8D638492DB252F000A96C0E /* TSAIListHistoryBaseCell.swift */,
 				A8BA76742DA67E65000B6707 /* TSAIListHistoryBaseVC.swift */,
 				A8BA76762DA68617000B6707 /* TSAIListHistoryBaseVM.swift */,
 			);
 			path = TSAIListHistoryBaseVC;
 			sourceTree = "<group>";
 		};
+		A8D6383A2DB1FC6E00A96C0E /* TSAIListVideoPlayerVC */ = {
+			isa = PBXGroup;
+			children = (
+				A8D6383B2DB1FC8A00A96C0E /* TSAIListVideoPlayerVC.swift */,
+			);
+			path = TSAIListVideoPlayerVC;
+			sourceTree = "<group>";
+		};
 		A8F413492DA75863001E715A /* TSUploadPhotoPrivacyAlertVC */ = {
 			isa = PBXGroup;
 			children = (
@@ -1518,11 +1534,12 @@
 		A8F774CE2D38EA8C00AA6E93 /* Tool */ = {
 			isa = PBXGroup;
 			children = (
+				A8D638452DB21FAD00A96C0E /* TSBusinessFileManager.swift */,
+				A8D638462DB21FAD00A96C0E /* TSDownloadManager.swift */,
 				A8D638342DB10BAB00A96C0E /* CrashReporter.swift */,
 				A8BA764E2DA50B52000B6707 /* CpuMapManager.swift */,
 				A85E479E2D6859F80018D62D /* TSRandomTextPicker.swift */,
 				A8F774C82D38EA8C00AA6E93 /* TSCommonTool */,
-				A8F774CA2D38EA8C00AA6E93 /* TSFileManagerTool.swift */,
 				A8F7754F2D39ECED00AA6E93 /* PhotoManager.swift */,
 				A834051F2DA3ADA600C140E4 /* TSPhotoSizeHelper.swift */,
 				A8F774CB2D38EA8C00AA6E93 /* TSNetworkTool.swift */,
@@ -2036,7 +2053,6 @@
 				A85E47C02D6961BB0018D62D /* TSChatMessageUIModel.swift in Sources */,
 				A8F774E02D38EA8C00AA6E93 /* TSCommonTool.swift in Sources */,
 				A89EA6BF2D5E03D6000EB181 /* Notification+Ex.swift in Sources */,
-				A8F774E12D38EA8C00AA6E93 /* TSFileManagerTool.swift in Sources */,
 				A80E73E42D533EB000C64288 /* TSPurchaseManager.swift in Sources */,
 				A80327C52D81584500AF7878 /* TSGeneralBtnView.swift in Sources */,
 				A8F7762F2D3A765400AA6E93 /* TSGenmojiViewModel.swift in Sources */,
@@ -2063,6 +2079,7 @@
 				A80EDD572D6C3F82003CD332 /* MarkdownCode+UIKit.swift in Sources */,
 				A80EDD582D6C3F82003CD332 /* String+UTF16.swift in Sources */,
 				A80EDD592D6C3F82003CD332 /* MarkdownLink+UIKit.swift in Sources */,
+				A8D6384A2DB252F100A96C0E /* TSAIListHistoryBaseCell.swift in Sources */,
 				A80EDD5A2D6C3F82003CD332 /* MarkdownParser.swift in Sources */,
 				A80EDD5B2D6C3F82003CD332 /* MarkdownCommonElement.swift in Sources */,
 				A80EDD682D6C5098003CD332 /* TSChatMsgBaseView.swift in Sources */,
@@ -2158,6 +2175,7 @@
 				A89EA66C2D59AA31000EB181 /* TSMessageContentCell.swift in Sources */,
 				A87587122D81702700286A66 /* TSUserDefaultData.swift in Sources */,
 				A8BA76422DA4C924000B6707 /* TSPTPInputVM.swift in Sources */,
+				A8D6383C2DB1FC8D00A96C0E /* TSAIListVideoPlayerVC.swift in Sources */,
 				A89EA66D2D59AA31000EB181 /* TableViewCells.swift in Sources */,
 				A8BA76472DA4CC70000B6707 /* TSPTPSelectStyleView.swift in Sources */,
 				A8F7750A2D38EA8C00AA6E93 /* TSNetworkTool.swift in Sources */,
@@ -2184,6 +2202,8 @@
 				A85E479B2D6808C40018D62D /* TSBigIconBrowseVC.swift in Sources */,
 				A80327B62D813D8700AF7878 /* TSTTPInputVM.swift in Sources */,
 				A89EA6B12D5C9D0C000EB181 /* TSAIChatHistoryVC.swift in Sources */,
+				A8D638472DB21FAD00A96C0E /* TSBusinessFileManager.swift in Sources */,
+				A8D638482DB21FAD00A96C0E /* TSDownloadManager.swift in Sources */,
 				A8FB02B72D3E3A3D0031A396 /* TSEmojisChildViewModel.swift in Sources */,
 				A8F7754B2D39376800AA6E93 /* TSSettingListView.swift in Sources */,
 				A8F7748B2D38E8B700AA6E93 /* AppDelegate.swift in Sources */,
@@ -2254,7 +2274,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 7;
+				CURRENT_PROJECT_VERSION = 1;
 				DEVELOPMENT_TEAM = 65UD255J84;
 				ENABLE_USER_SCRIPT_SANDBOXING = NO;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -2270,7 +2290,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 3.3.1;
+				MARKETING_VERSION = 3.3.2;
 				PRODUCT_BUNDLE_IDENTIFIER = com.girl.music.wallpaper;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
@@ -2293,7 +2313,7 @@
 				ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_STYLE = Automatic;
-				CURRENT_PROJECT_VERSION = 7;
+				CURRENT_PROJECT_VERSION = 1;
 				DEVELOPMENT_TEAM = 65UD255J84;
 				ENABLE_USER_SCRIPT_SANDBOXING = NO;
 				GENERATE_INFOPLIST_FILE = YES;
@@ -2309,7 +2329,7 @@
 					"$(inherited)",
 					"@executable_path/Frameworks",
 				);
-				MARKETING_VERSION = 3.3.1;
+				MARKETING_VERSION = 3.3.2;
 				PRODUCT_BUNDLE_IDENTIFIER = com.girl.music.wallpaper;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";

+ 22 - 0
AIEmoji/Assets.xcassets/AIList/aiList_livePhoto.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "aiList_livePhoto@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "aiList_livePhoto@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

binární
AIEmoji/Assets.xcassets/AIList/aiList_livePhoto.imageset/aiList_livePhoto@2x.png


binární
AIEmoji/Assets.xcassets/AIList/aiList_livePhoto.imageset/aiList_livePhoto@3x.png


+ 22 - 0
AIEmoji/Assets.xcassets/Common/pause.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "pause@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "pause@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

binární
AIEmoji/Assets.xcassets/Common/pause.imageset/pause@2x.png


binární
AIEmoji/Assets.xcassets/Common/pause.imageset/pause@3x.png


+ 22 - 0
AIEmoji/Assets.xcassets/Common/play.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "play@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "play@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

binární
AIEmoji/Assets.xcassets/Common/play.imageset/play@2x.png


binární
AIEmoji/Assets.xcassets/Common/play.imageset/play@3x.png


+ 22 - 0
AIEmoji/Assets.xcassets/Common/video_icon.imageset/Contents.json

@@ -0,0 +1,22 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "video_icon@2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "video_icon@3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

binární
AIEmoji/Assets.xcassets/Common/video_icon.imageset/video_icon@2x.png


binární
AIEmoji/Assets.xcassets/Common/video_icon.imageset/video_icon@3x.png


+ 12 - 4
AIEmoji/Business/Data/TSUserDefaultData.swift

@@ -235,10 +235,18 @@ final class TSAIPhotoPrettyHistory: TSBaseHistoryManager<TSActionInfoModel> {
     }
 }
 
-
-
-
-
+// MARK: - 照片变活
+final class TSAIPhotoLiveHistory: TSBaseHistoryManager<TSActionInfoModel> {
+    static let shared = TSAIPhotoLiveHistory()
+    override var historyKey: String { "kTSAIPhotoLiveHistoryListString" }
+    
+    override var exampleModels: [TSActionInfoModel] {
+        []
+    }
+    override func findModelID(modelID: Int) -> Int? {
+        return listModels.firstIndex(where: {$0.id == modelID})
+    }
+}
 
 
 

+ 18 - 0
AIEmoji/Business/TSAILIstVC/TSAILIstVC/TSAILIstVC.swift

@@ -24,6 +24,24 @@ class TSAILIstVC: TSBaseVC {
                     kPushVC(target: self, modelVC: TSTextGeneralPictureVC())
         }))
         
+        sectionModel.addSubItemModel(
+            createItemModel(
+                leftImageName:"aiList_livePhoto",
+                leftTitle: "Make photo live".localized,
+                leftSubTitle: "".localized,
+                rightViewStyle: 0,
+                tapBlock: { [weak self] model, _, _ in
+                   guard let self = self else { return }
+                    enterSelectPhotos(
+                        userDefaultsKey: "isFirstAILivePhoto",
+                        maxBitSize: kUploadImageMaxBit5Size,
+                         config:.getDefaultConfig(imageMaxBitSize: kUploadImageMaxBit5Size)
+                    ) { image in
+                        let baseVc = TSAIUploadPhotoBaseVC(titleString: model.leftTitle ?? "",upLoadImage: image,imageMaxBitSize: kUploadImageMaxBit5Size, generatorStyle: .photoLive)
+                        kPushVC(target: self, modelVC: baseVc)
+                    }
+        }))
+        
         sectionModel.addSubItemModel(
             createItemModel(
                 leftImageName:"ailist_pretty",

+ 91 - 0
AIEmoji/Business/TSAILIstVC/TSAIListHistoryBaseVC/TSAIListHistoryBaseCell.swift

@@ -0,0 +1,91 @@
+//
+//  TSAIListHistoryBaseCell.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/4/18.
+//
+
+class TSAIListHistoryBaseCell: TSBaseCollectionCell {
+    
+    lazy var textLabel: UILabel = {
+        let textLabel = UILabel.createLabel(
+            text: "Example".localized,
+            font: .font(size: 12),
+            textColor: .white
+        )
+        return textLabel
+    }()
+    
+    lazy var exampleView: UIView = {
+        let exampleView = UIView()
+        exampleView.backgroundColor = "#232323".uiColor.withAlphaComponent(0.3)
+        
+        exampleView.addSubview(textLabel)
+        textLabel.snp.makeConstraints { make in
+            make.top.edges.equalTo(UIEdgeInsets(top: 4, left: 6, bottom: 4, right: 6))
+        }
+        exampleView.isHidden = true
+        exampleView.cornerRadius = 10.0
+        return exampleView
+    }()
+    
+    lazy var showImageView: UIImageView = {
+        let showImageView = UIImageView.createImageView(imageName:"",contentMode: .scaleAspectFill)
+        showImageView.backgroundColor = .gray
+        showImageView.layer.cornerRadius = 18
+        return showImageView
+    }()
+    
+    lazy var videoIconImageView: UIImageView = {
+        let videoIconImageView = UIImageView.createImageView(imageName:"video_icon",contentMode: .scaleToFill)
+        videoIconImageView.isHidden = true
+        return videoIconImageView
+    }()
+    
+    
+    override func creatUI() {
+        contentView.addSubview(showImageView)
+        showImageView.snp.makeConstraints { make in
+            make.top.equalTo(0)
+            make.leading.equalTo(0)
+            make.trailing.bottom.equalTo(0)
+        }
+        
+        contentView.addSubview(exampleView)
+        exampleView.snp.makeConstraints { make in
+            make.top.equalTo(8)
+            make.leading.equalTo(8)
+            make.height.equalTo(20)
+        }
+        
+        contentView.addSubview(videoIconImageView)
+        videoIconImageView.snp.makeConstraints { make in
+            make.top.equalTo(8)
+            make.leading.equalTo(8)
+            make.width.height.equalTo(24)
+        }
+    }
+    
+    override func renderView(with object: Any?, component: TSCollectionViewComponent, attributes: [String : Any]?) {
+        super.renderView(with: object, component: component, attributes: attributes)
+        if let itemModel = object as? TSGenmojiCoLItemModel{
+            videoIconImageView.isHidden = true
+            exampleView.isHidden = true
+            if itemModel.dataModel.modelType == .example {
+                if itemModel.style == .ptpPicHistory {
+                    textLabel.text = "Example".localized
+                }
+                exampleView.isHidden = false
+                showImageView.image = UIImage(named: itemModel.dataModel.response.resultUrl)
+            }else{
+                if itemModel.dataModel.isVideo {
+                    videoIconImageView.isHidden = false
+                    self.showImageView.image = UIImage(contentsOfFile: itemModel.dataModel.videoThumbnailURL.path)
+                }else {
+                    showImageView.setAsyncImage(urlString: itemModel.dataModel.response.resultUrl,contentMode: .scaleAspectFill,backgroundColor: .white.withAlphaComponent(0.1))
+                }
+            }
+        }
+    }
+    
+}

+ 4 - 0
AIEmoji/Business/TSAILIstVC/TSAIListHistoryBaseVC/TSAIListHistoryBaseVM.swift

@@ -49,6 +49,8 @@ class TSAIListHistoryBaseVM {
             TSAIEyeOpenHistory.shared.removeALLModel()
         case .pretty:
             TSAIPhotoPrettyHistory.shared.removeALLModel()
+        case .photoLive:
+            TSAIPhotoLiveHistory.shared.removeALLModel()
         }
         
         colDataArray.removeAll()
@@ -69,6 +71,8 @@ extension TSAIListHistoryBaseVM {
             TSAIEyeOpenHistory.shared.listModels
         case .pretty:
             TSAIPhotoPrettyHistory.shared.listModels
+        case .photoLive:
+            TSAIPhotoLiveHistory.shared.listModels
         }
     }
 }

+ 272 - 0
AIEmoji/Business/TSAILIstVC/TSAIListVideoPlayerVC/TSAIListVideoPlayerVC.swift

@@ -0,0 +1,272 @@
+//
+//  TSVideoPlayerVC.swift
+//  AIEmoji
+//
+//  Created by 100Years on 2025/4/17.
+//
+
+
+import UIKit
+import AVKit
+import SnapKit
+
+class TSAIListVideoPlayerVC: UIViewController {
+    
+    // MARK: - Properties
+    private var player: AVPlayer?
+    private var playerLayer: AVPlayerLayer?
+    private var timeObserverToken: Any?
+    private var isPlaying = false
+    
+    private let videoURL: URL
+    
+    // MARK: - UI Components
+    private lazy var playerContainerView: UIView = {
+        let view = UIView()
+        view.backgroundColor = .black
+        return view
+    }()
+    
+    private lazy var playPauseButton: UIButton = {
+        let button = UIButton()
+        button.setImage(UIImage(named: "play"), for: .normal)
+        button.tintColor = .white
+//        button.addTarget(self, action: #selector(playPauseTapped), for: .touchUpInside)
+        button.isUserInteractionEnabled = false
+        return button
+    }()
+    
+    private lazy var progressSlider: UISlider = {
+        let slider = UISlider()
+        slider.minimumTrackTintColor = UIColor.themeColor
+        slider.maximumTrackTintColor = .white.withAlphaComponent(0.2)
+//        slider.thumbTintColor = UIColor.white
+//        slider.setMinimumTrackImage(UIImage(color: UIColor.themeColor, size: CGSize(width: 1, height: 3)), for: .normal)
+//        slider.setMaximumTrackImage(UIImage(color: .white.withAlphaComponent(0.2), size: CGSize(width: 1, height: 3)), for: .normal)
+        slider.setThumbImage(UIImage.circle(diameter: 10, color: .white), for: .normal)
+        
+        slider.addTarget(self, action: #selector(sliderValueChanged(_:)), for: .valueChanged)
+        slider.addTarget(self, action: #selector(sliderTouchEnded(_:)), for: .touchUpInside)
+        slider.addTarget(self, action: #selector(sliderTouchEnded(_:)), for: .touchUpOutside)
+        return slider
+    }()
+    
+    private lazy var currentTimeLabel: UILabel = {
+        let label = UILabel.createLabel(text: "00:00",font:.font(size: 12),textColor: .white)
+        return label
+    }()
+    
+    private lazy var durationLabel: UILabel = {
+        let label = UILabel.createLabel(text: "00:00",font:.font(size: 12),textColor: .white)
+        return label
+    }()
+
+    private lazy var controlsContainerView: UIView = {
+        let view = UIView()
+//        view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
+//        view.layer.cornerRadius = 8
+        return view
+    }()
+    
+    // MARK: - Initialization
+    init(videoURL: URL) {
+        self.videoURL = videoURL
+        super.init(nibName: nil, bundle: nil)
+    }
+    
+    required init?(coder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+    
+    // MARK: - Lifecycle
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        setupUI()
+        setupPlayer()
+    }
+    
+    override func viewDidLayoutSubviews() {
+        super.viewDidLayoutSubviews()
+        playerLayer?.frame = playerContainerView.bounds
+    }
+    
+    deinit {
+        removePeriodicTimeObserver()
+    }
+    
+    // MARK: - Setup
+    private func setupUI() {
+        view.backgroundColor = .black
+        playerContainerView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(playPauseTapped)))
+        view.addSubview(playerContainerView)
+        playerContainerView.addSubview(playPauseButton)
+
+        playerContainerView.snp.makeConstraints { make in
+            make.edges.equalToSuperview()
+        }
+
+        playPauseButton.snp.makeConstraints { make in
+            make.centerX.equalToSuperview()
+            make.centerY.equalToSuperview()//.offset(-50)
+            make.width.height.equalTo(56)
+        }
+        
+        playerContainerView.addSubview(controlsContainerView)
+        controlsContainerView.snp.makeConstraints { make in
+            make.leading.trailing.equalTo(0)
+            make.bottom.equalTo(-80-k_Height_safeAreaInsetsBottom())
+        }
+        
+        controlsContainerView.addSubview(progressSlider)
+        controlsContainerView.addSubview(currentTimeLabel)
+        controlsContainerView.addSubview(durationLabel)
+        
+        progressSlider.snp.makeConstraints { make in
+            make.leading.equalTo(16)
+            make.trailing.equalTo(-16)
+            make.top.equalTo(0)
+            make.height.equalTo(10)
+        }
+        
+        let label = UILabel.createLabel(text: "/",font: .font(size: 11),textColor: .white)
+        controlsContainerView.addSubview(label)
+        label.snp.makeConstraints { make in
+            make.top.equalTo(progressSlider.snp.bottom).offset(6)
+            make.centerX.equalToSuperview()
+            make.height.equalTo(13)
+            make.bottom.equalToSuperview()
+        }
+        
+        currentTimeLabel.snp.makeConstraints { make in
+            make.height.equalTo(13)
+            make.centerY.equalTo(label)
+            make.trailing.equalTo(label.snp.leading)
+        }
+        
+        durationLabel.snp.makeConstraints { make in
+            make.height.equalTo(13)
+            make.centerY.equalTo(label)
+            make.leading.equalTo(label.snp.trailing)
+        }
+    }
+
+    private func setupPlayer() {
+        player = AVPlayer(url: videoURL)
+        playerLayer = AVPlayerLayer(player: player)
+        playerLayer?.videoGravity = .resizeAspect
+        
+        if let playerLayer = playerLayer {
+            playerContainerView.layer.insertSublayer(playerLayer, at: 0)
+        }
+        
+        // Add time observer to update progress
+        addPeriodicTimeObserver()
+        
+        // Observe when the video ends
+        NotificationCenter.default.addObserver(
+            self,
+            selector: #selector(playerDidFinishPlaying),
+            name: .AVPlayerItemDidPlayToEndTime,
+            object: player?.currentItem
+        )
+        
+        // Get video duration
+        let duration = player?.currentItem?.asset.duration.seconds ?? 0
+        durationLabel.text = formatTime(seconds: Float(duration))
+    }
+    
+    func setControlsBottom(bottem:CGFloat){
+        controlsContainerView.snp.updateConstraints { make in
+            make.bottom.equalTo(bottem)
+        }
+    }
+    
+    // MARK: - Player Controls
+    @objc private func playPauseTapped() {
+        if isPlaying {
+            playPause()
+        } else {
+            playPlay()
+        }
+    }
+    
+    @objc private func playPlay() {
+        player?.play()
+        playPauseButton.isHidden = true
+//        playPauseButton.setImage(UIImage(named: "pause"), for: .normal)
+        isPlaying = true
+    }
+    
+    @objc func playPause() {
+        player?.pause()
+        playPauseButton.isHidden = false
+//        playPauseButton.setImage(UIImage(named: "play"), for: .normal)
+        isPlaying = false
+    }
+    
+    @objc private func playerDidFinishPlaying() {
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1){
+            self.playerDidFinish()
+        }
+    }
+    
+    func playerDidFinish() {
+        player?.seek(to: CMTime.zero)
+        playPauseButton.isHidden = false
+//        playPauseButton.setImage(UIImage(named: "play"), for: .normal)
+        isPlaying = false
+        progressSlider.value = 0
+        currentTimeLabel.text = "00:00"
+    }
+    // MARK: - Progress Slider
+    @objc private func sliderValueChanged(_ sender: UISlider) {
+        playPause()
+        guard let duration = player?.currentItem?.duration else { return }
+        let totalSeconds = CMTimeGetSeconds(duration)
+        let value = Float64(sender.value) * totalSeconds
+        let seekTime = CMTime(value: Int64(value), timescale: 1)
+        currentTimeLabel.text = formatTime(seconds: Float(value))
+    }
+    
+    @objc private func sliderTouchEnded(_ sender: UISlider) {
+        guard let duration = player?.currentItem?.duration else { return }
+        let totalSeconds = CMTimeGetSeconds(duration)
+        let value = Float64(sender.value) * totalSeconds
+        let seekTime = CMTime(value: Int64(value), timescale: 1)
+        player?.seek(to: seekTime, toleranceBefore: .zero, toleranceAfter: .zero)
+        playPlay()
+    }
+    
+    // MARK: - Time Observer
+    private func addPeriodicTimeObserver() {
+        let interval = CMTime(seconds: 0.05, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
+        
+        timeObserverToken = player?.addPeriodicTimeObserver(
+            forInterval: interval,
+            queue: .main
+        ) { [weak self] time in
+            guard let self = self else { return }
+            let timeElapsed = Float(time.seconds)
+            
+            if let duration = self.player?.currentItem?.duration {
+                let durationSeconds = Float(CMTimeGetSeconds(duration))
+                self.progressSlider.value = Float(timeElapsed / durationSeconds)
+                self.currentTimeLabel.text = self.formatTime(seconds: timeElapsed)
+            }
+        }
+    }
+    
+    private func removePeriodicTimeObserver() {
+        if let token = timeObserverToken {
+            player?.removeTimeObserver(token)
+            timeObserverToken = nil
+        }
+    }
+    
+    // MARK: - Helper Methods
+    private func formatTime(seconds: Float) -> String {
+        let minutes = Int(seconds) / 60
+        let seconds = Int(seconds) % 60
+        return String(format: "%02d:%02d", minutes, seconds)
+    }
+}

+ 54 - 8
AIEmoji/Business/TSAILIstVC/TSAIPhotoGeneratorBaseVC/TSAIListPhotoGeneratorBaseVC.swift

@@ -32,6 +32,7 @@ class TSAIListPhotoGeneratorBaseVC: TSAIPhotoGeneratorBaseVC {
         super.init()
     }
     
+    var videoPlayerVC: TSAIListVideoPlayerVC = TSAIListVideoPlayerVC(videoURL: URL(string: "www.baidu.com")!)
     lazy var viewModel: TSAIListPhotoGeneratorBaseVM = {
         let viewModel:TSAIListPhotoGeneratorBaseVM = TSAIListPhotoGeneratorBaseVM(upLoadImage: upLoadImage, generatorStyle: generatorStyle)
         return viewModel
@@ -146,14 +147,31 @@ class TSAIListPhotoGeneratorBaseVC: TSAIPhotoGeneratorBaseVC {
     
     //保存功能
     @objc override func clickSaveBtn(){
-        if let image = getSuccessImage() {
-            PhotoManagerShared.saveImageToAlbum(image) { [weak self] success, error in
-                guard let self = self else { return }
-                if success {
-                    isSavePhotoMark = true
-                    kSavePhotoSuccesswShared.show(atView:self.view)
-                }else{
-                    debugPrint(error)
+    
+        if generatorStyle == .photoLive,let imageModel = imageModel {
+            TSDownloadManager.getDownLoadVideo(urlString: imageModel.response.resultUrl) { url, success in
+                if let url = url {
+                    PhotoManagerShared.saveVideoToAlbum(videoURL: url) { [weak self] success, error in
+                        guard let self = self else { return }
+                        if success {
+                            isSavePhotoMark = true
+                            kSavePhotoSuccesswShared.show(atView:self.view)
+                        }else{
+                            debugPrint(error)
+                        }
+                    }
+                }
+            }
+        }else{
+            if let image = getSuccessImage() {
+                PhotoManagerShared.saveImageToAlbum(image) { [weak self] success, error in
+                    guard let self = self else { return }
+                    if success {
+                        isSavePhotoMark = true
+                        kSavePhotoSuccesswShared.show(atView:self.view)
+                    }else{
+                        debugPrint(error)
+                    }
                 }
             }
         }
@@ -206,6 +224,8 @@ extension TSAIListPhotoGeneratorBaseVC {
         bottomView.isHidden = true
         netWorkImageView.isHidden = true
         switchOriginalPictureBtn.isHidden = true
+        
+        setVideoHidden()
     }
     
     func showLoading(){
@@ -215,6 +235,8 @@ extension TSAIListPhotoGeneratorBaseVC {
         bottomView.isHidden = true
         netWorkImageView.isHidden = true
         switchOriginalPictureBtn.isHidden = true
+        
+        setVideoHidden()
     }
     
     func showError(text:String?){
@@ -228,6 +250,8 @@ extension TSAIListPhotoGeneratorBaseVC {
         bottomView.isHidden = false
         netWorkImageView.isHidden = true
         switchOriginalPictureBtn.isHidden = true
+        
+        setVideoHidden()
     }
     
     func showSuccess(model:TSActionInfoModel){
@@ -248,6 +272,8 @@ extension TSAIListPhotoGeneratorBaseVC {
         if let model = imageModel {
             complete(model)
         }
+        
+        setVideoURL()
     }
     
     @objc func switchOriginalPictureTouchDown() {
@@ -258,4 +284,24 @@ extension TSAIListPhotoGeneratorBaseVC {
         guard let imageModel = imageModel else { return }
         self.netWorkImageView.setAsyncImage(urlString: imageModel.response.resultUrl,placeholder:kPlaceholderImage,backgroundColor:netWorkImageView.backgroundColor!)
     }
+    
+    func setVideoHidden(){
+        if generatorStyle == .photoLive {
+            videoPlayerVC.removeFromParent()
+            videoPlayerVC.view.removeFromSuperview()
+        }
+    }
+    
+    func setVideoURL(){
+        if generatorStyle == .photoLive {
+            if let model = imageModel {
+                switchOriginalPictureBtn.isHidden = true
+                self.videoPlayerVC = TSAIListVideoPlayerVC(videoURL: model.videoURL)
+                self.addChild(self.videoPlayerVC)
+                self.videoPlayerVC.view.frame = self.netWorkImageView.bounds
+                self.netWorkImageView.addSubview(self.videoPlayerVC.view)
+                self.videoPlayerVC.setControlsBottom(bottem: -20)
+            }
+        }
+    }
 }

+ 71 - 22
AIEmoji/Business/TSAILIstVC/TSAIPhotoGeneratorBaseVC/TSAIPhotoGeneratorBaseVM/TSAIListPhotoGeneratorBaseVM.swift

@@ -7,6 +7,21 @@
 
 import Alamofire
 
+
+
+let actionInfoDictVideo:[String:Any] = [
+    "actionType":"image_animation",
+    "comments": "Success",
+    "costTime":11,
+    "createdTimestamp":1744972580,
+    "id":32743,
+    "percent":1,
+    "request":"{\\\"imageUrl\\\": \\\"https://be-aigc.oss-cn-shanghai.aliyuncs.com/1981095e-b810-4eef-923b-5540350ae2d7.jpeg\\\", \\\"countryCode\\\": \\\"CN\\\", \\\"ip\\\": \\\"120.229.53.7\\\"}",
+    "response": "{\\\"resultUrl\\\": \\\"https://be-aigc.s3-accelerate.amazonaws.com/4e0da626-1de7-4c2e-9be2-3e6d0742a28e.mp4\\\"}",
+    "status":"success"
+]
+
+
 class TSAIListPhotoGeneratorBaseVM {
     
     var uploadRequest:Request?
@@ -34,7 +49,7 @@ class TSAIListPhotoGeneratorBaseVM {
 //        kDelayOnMainThread(0.2) {
 //            self.stateDatauPblished = (.progressString(self.generating(progress: 0.2)),nil)
 //        }
-//
+//        
 //        kDelayOnMainThread(0.5) {
 //            self.stateDatauPblished = (.progressString(self.generating(progress: 0.5)),nil)
 //        }
@@ -43,10 +58,9 @@ class TSAIListPhotoGeneratorBaseVM {
 //            self.stateDatauPblished = (.progressString(self.generating(progress: 0.8)),nil)
 //        }
 //
-//
 //        kDelayOnMainThread(2.0) {
 //            if kRandomBool() {
-//                let infoModel = TSActionInfoModel(JSON:actionInfoDictPoster )
+//                let infoModel = TSActionInfoModel(JSON: self.generatorStyle == .photoLive ? actionInfoDictVideo : actionInfoDictPoster)
 //                self.stateDatauPblished = (.success(nil),infoModel)
 //            }else{
 //                self.stateDatauPblished = (.failed("error?.localizedDescription"),nil)
@@ -106,13 +120,6 @@ class TSAIListPhotoGeneratorBaseVM {
                         "device":getUserInfoJsonString()
                        ]
             case .oldPhoto:
-//            urlType = .imageRewrite
-//            postDict = ["prompt":"修复上传的老照片,保持人物表情和动作100%不变,去除划痕、污渍和折痕,修复褪色发黄,增强清晰度(超分辨率)",
-//                        "imageUrl":imageUrl,
-//                        "style":"No Style",
-//                        "device":getUserInfoJsonString()
-//                       ]
-            
             urlType = .imageRestore
             postDict = ["imageUrl":imageUrl,
                         "device":getUserInfoJsonString()
@@ -128,6 +135,11 @@ class TSAIListPhotoGeneratorBaseVM {
                         "level":1.0,
                         "device":getUserInfoJsonString()
                        ]
+        case .photoLive:
+            urlType = .photoAnimation
+            postDict = ["imageUrl":imageUrl,
+                        "device":getUserInfoJsonString()
+                       ]
         }
         creatRequest = TSNetworkShared.post(urlType: urlType,parameters: postDict) { [weak self] data,error in
             guard let self = self else { return }
@@ -152,19 +164,31 @@ class TSAIListPhotoGeneratorBaseVM {
                     switch genmojiModel.actionStatus {
                     case .success:
                         if let url = URL(string:genmojiModel.response.resultUrl) {
-                            UIImageView.downloadImageWithProgress(urlString: genmojiModel.response.resultUrl) { [weak self]  progress in
-                                guard let self = self else { return }
-                        
-                                let progressInt = Int(progress*10.0)
-                                let progressString = generatingText + " \(90 + progressInt)%"
-                                stateDatauPblished = (.progressString(progressString),nil)
-                                dePrint("生成后图片下载进度: \(progress)")
-                            } completion: {[weak self] image in
-                                guard let self = self else { return }
-                                self.stateDatauPblished = (.success(nil),genmojiModel)
-                                generatingProgress = 0
-                            }
+                            if generatorStyle == .photoLive {
+                                downloadVideo(urlString: genmojiModel.response.resultUrl) { url in
+                                    if let url = url {
+                                        
+                                        genmojiModel.videoPath = url.path.documentLastURLString
+                                        if let videoImage = kGetVideoThumbnail(from: url) {
+                                            let imageSavePath = url.deletingPathExtension().path + ".jpeg"
+                                            videoImage.saveToFile(at:URL(fileURLWithPath:imageSavePath))
+                                            genmojiModel.videoThumbnailPath = imageSavePath.documentLastURLString
+                                        }
 
+                                        self.stateDatauPblished = (.success(nil),genmojiModel)
+                                        self.generatingProgress = 0
+                                    }else{
+                                        self.stateDatauPblished = (.failed(kNetWorkMessage(data: data) ?? ""),nil)
+                                        self.generatingProgress = 0
+                                    }
+                                }
+                            }else {
+                                downloadImage(urlString: genmojiModel.response.resultUrl) { [weak self] in
+                                    guard let self = self else { return }
+                                    self.stateDatauPblished = (.success(nil),genmojiModel)
+                                    generatingProgress = 0
+                                }
+                            }
                         }else{
                             self.stateDatauPblished = (.success(nil),genmojiModel)
                             generatingProgress = 0
@@ -188,6 +212,29 @@ class TSAIListPhotoGeneratorBaseVM {
         }
     }
     
+    func downloadImage(urlString:String,completion:@escaping ()->Void){
+        UIImageView.downloadImageWithProgress(urlString: urlString) { [weak self]  progress in
+            guard let self = self else { return }
+    
+            let progressInt = Int(progress*10.0)
+            let progressString = generatingText + " \(90 + progressInt)%"
+            stateDatauPblished = (.progressString(progressString),nil)
+            dePrint("生成后图片下载进度: \(progress)")
+        } completion: { image in
+            completion()
+        }
+    }
+    
+    func downloadVideo(urlString:String,completion:@escaping (URL?)->Void){
+        TSDownloadManager.getDownLoadVideo(urlString: urlString) { progress in
+            let progressInt = Int(progress*10.0)
+            let progressString = self.generatingText + " \(90 + progressInt)%"
+            self.stateDatauPblished = (.progressString(progressString),nil)
+            dePrint("生成后视频下载进度: \(progress)")
+        } complete: { url, _ in
+            completion(url)
+        }
+    }
     
     func cancelAllRequest(){
         creatRequest?.cancel()
@@ -207,6 +254,8 @@ class TSAIListPhotoGeneratorBaseVM {
             return 5*1024
         case .pretty:
             return 5*1024
+        case .photoLive:
+            return 10*1024
         }
     }
     

+ 3 - 0
AIEmoji/Business/TSAILIstVC/TSAIUploadPhotoBaseVC/TSAIUploadPhotoBaseVC.swift

@@ -12,6 +12,7 @@ enum TSGeneratorImageStyle {
     case oldPhoto   //旧照片修复
     case eyeOpen    //开眼
     case pretty     //变美
+    case photoLive     //把照片变活
 }
 
 class TSAIUploadPhotoBaseVC: TSBaseVC {
@@ -286,6 +287,8 @@ extension TSAIUploadPhotoBaseVC {
             TSAIEyeOpenHistory.shared.saveModel(model: model)
         case .pretty:
             TSAIPhotoPrettyHistory.shared.saveModel(model: model)
+        case .photoLive:
+            TSAIPhotoLiveHistory.shared.saveModel(model: model)
         }
     }
 

+ 18 - 1
AIEmoji/Business/TSGenmojiVC/TSGenmojiVC/Model/TSGenmojiModel.swift

@@ -35,7 +35,8 @@ class TSActionInfoModel: TSBaseModel {
     var percent:Float = 0.0
     var actionStatus:ActionStatus = .failed
     
-    
+    var videoThumbnailPath:String = ""
+    var videoPath:String = ""
     override func mapping(map: ObjectMapper.Map) {
         modelType           <- map["modelType"]
         id           <- map["id"]
@@ -49,6 +50,22 @@ class TSActionInfoModel: TSBaseModel {
         percent     <- map["percent"]
         actionStatus      <- map["actionStatus"]
         actionStatus = ActionStatus.from(status)
+        videoThumbnailPath      <- map["videoThumbnailPath"]
+        videoPath      <- map["videoPath"]
+    }
+}
+
+extension TSActionInfoModel {
+    var isVideo:Bool{
+        return videoThumbnailPath.count > 0
+    }
+    
+    var videoThumbnailURL: URL {
+        return videoThumbnailPath.fillDocumentURL
+    }
+    
+    var videoURL: URL {
+        return videoPath.fillDocumentURL
     }
 }
 

+ 1 - 1
AIEmoji/Business/TSGenmojiVC/TSGenmojiVC/View/TSGenmojiItemCell.swift

@@ -66,7 +66,7 @@ class TSGenmojiItemCell: TSBaseCollectionCell {
                 showImageView.image = UIImage(named: itemModel.dataModel.response.resultUrl)
             }else{
                 exampleView.isHidden = true
-                showImageView.setAsyncImage(urlString: itemModel.dataModel.response.resultUrl,contentMode: .scaleAspectFill)
+                showImageView.setAsyncImage(urlString: itemModel.dataModel.response.resultUrl,contentMode: .scaleAspectFill,backgroundColor: .white.withAlphaComponent(0.1))
             }
         }
     }

+ 4 - 1
AIEmoji/Business/TSGenmojiVC/TSGenmojiVC/ViewModel/TSGenmojiCollectionViewModel.swift

@@ -122,7 +122,7 @@ enum TSGenmojiCoLStyple : Int {
         switch self {
         case .generate,.textPicGenerate:
             return TSGenmojiGennerateCell.self
-        case .history,.textPicHistory,.ptpPicHistory,.changeAgeHistory:
+        case .history,.textPicHistory,.ptpPicHistory:
             return TSGenmojiItemCell.self
         case .ptpEntrance:
             return TSPTPGeneratorCell.self
@@ -130,6 +130,9 @@ enum TSGenmojiCoLStyple : Int {
             return TSPTPUploadCell.self
         case .ptpSelectStyle:
             return TSPTPSelectStyleCell.self
+        case .changeAgeHistory:
+            return TSAIListHistoryBaseCell.self
+            
         }
     }
     

+ 75 - 10
AIEmoji/Business/TSPTPGeneratorVC/TSAIPhotoGeneratorBaseVC/TSAIPhotoBrowseVC.swift

@@ -6,6 +6,7 @@
 //
 
 private let cellId = "TSAIPhotoBrowseCell"
+private let videoCellId = "TSAIVideoBrowseCell"
 class TSAIPhotoBrowseVC: TSBaseVC {
 
     var dataModelArray = [TSActionInfoModel]()
@@ -45,6 +46,8 @@ class TSAIPhotoBrowseVC: TSBaseVC {
             collectionView.contentInsetAdjustmentBehavior = .never
         }
         collectionView.register(TSAIPhotoBrowseCell.self, forCellWithReuseIdentifier: cellId)
+        collectionView.register(TSAIVideoBrowseCell.self, forCellWithReuseIdentifier: videoCellId)
+        
         collectionView.isPagingEnabled = true
         collectionView.isHidden = true
         if let flowLayout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
@@ -127,16 +130,33 @@ class TSAIPhotoBrowseVC: TSBaseVC {
             return
         }
         
-        if let image = currentImage{
-            PhotoManagerShared.saveImageToAlbum(image) { success, error in
-                if success {
-                    kSavePhotoSuccesswShared.show(atView: self.view)
-                }else{
-                    debugPrint(error)
+        guard let currentModel = currentModel else { return }
+        let urlString = currentModel.response.resultUrl
+        if currentModel.isVideo {
+            TSDownloadManager.getDownLoadVideo(urlString: urlString) { url, _ in
+                if let url = url {
+                    PhotoManagerShared.saveVideoToAlbum(videoURL: url) { success, error in
+                        if success {
+                            kSavePhotoSuccesswShared.show(atView: self.view)
+                        }else{
+                            debugPrint(error)
+                        }
+                    }
                 }
             }
         }else{
-            kShowToastDataMissing()
+            UIImageView.downloadImageWithProgress(urlString: urlString) { image in
+                if let image = image{
+                    PhotoManagerShared.saveImageToAlbum(image) { success, error in
+                        if success {
+                            kSavePhotoSuccesswShared.show(atView: self.view)
+                        }else{
+                            debugPrint(error)
+                        }
+                    }
+                }
+                
+            }
         }
     }
     
@@ -198,6 +218,14 @@ extension TSAIPhotoBrowseVC:UICollectionViewDataSource,UICollectionViewDelegate
     }
     
     func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        if let model = dataModelArray.safeObj(At: indexPath.item){
+            if model.isVideo {
+                let cell = collectionView.dequeueReusableCell(withReuseIdentifier: videoCellId, for: indexPath) as! TSAIVideoBrowseCell
+                cell.model = model
+                return cell
+            }
+        }
+        
         let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! TSAIPhotoBrowseCell
         if let model = dataModelArray.safeObj(At: indexPath.item){
             cell.model = model
@@ -206,10 +234,26 @@ extension TSAIPhotoBrowseVC:UICollectionViewDataSource,UICollectionViewDelegate
     }
     
     func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
-        guard let cell = cell as? TSAIPhotoBrowseCell else { return }
-        
         if let model = dataModelArray.safeObj(At: indexPath.item){
-            cell.model = model
+            if model.isVideo {
+                guard let cell = cell as? TSAIVideoBrowseCell else { return }
+                cell.model = model
+            }else{
+                guard let cell = cell as? TSAIPhotoBrowseCell else { return }
+                cell.model = model
+            }
+        }
+    }
+    
+    func collectionView(_ collectionView: UICollectionView,
+                      didEndDisplaying cell: UICollectionViewCell,
+                      forItemAt indexPath: IndexPath) {
+
+        if let model = dataModelArray.safeObj(At: indexPath.item){
+            if model.isVideo {
+                guard let cell = cell as? TSAIVideoBrowseCell else { return }
+                cell.videoPlayerVC?.playPause()
+            }
         }
     }
     
@@ -285,3 +329,24 @@ class TSAIPhotoBrowseCell : TSBaseCollectionCell{
         }
     }
 }
+
+class TSAIVideoBrowseCell : TSBaseCollectionCell{
+
+    var videoPlayerVC: TSAIListVideoPlayerVC?
+    override func creatUI() {
+
+    }
+    
+    var model:TSActionInfoModel = TSActionInfoModel(){
+        didSet{
+            self.videoPlayerVC?.view.removeFromSuperview()
+            self.videoPlayerVC = TSAIListVideoPlayerVC(videoURL: self.model.videoURL)
+            self.bgContentView.addSubview(self.videoPlayerVC!.view)
+            self.videoPlayerVC!.view.snp.remakeConstraints { make in
+                make.center.equalToSuperview()
+                make.width.equalTo(k_ScreenWidth)
+                make.height.equalTo(k_ScreenHeight)
+            }
+        }
+    }
+}

+ 3 - 2
AIEmoji/Business/TSPTPGeneratorVC/TSAIPhotoGeneratorBaseVC/TSAIPhotoGeneratorBaseVC.swift

@@ -15,9 +15,10 @@ class TSAIPhotoGeneratorBaseVC: TSBaseVC {
     
     
     lazy var netWorkImageView: UIImageView = {
-        let netWorkImageView = UIImageView(frame: CGRectMake(0, 0, k_ScreenWidth, k_ScreenHeight))
+        let netWorkImageView = UIImageView(frame: CGRectMake(0, 0, k_ScreenWidth, k_ScreenHeight-bottomViewH))
         netWorkImageView.backgroundColor = "#111111".uiColor
         netWorkImageView.contentMode = .scaleAspectFit
+        netWorkImageView.isUserInteractionEnabled = true
         return netWorkImageView
     }()
     
@@ -79,7 +80,7 @@ class TSAIPhotoGeneratorBaseVC: TSBaseVC {
     var isClickTheBlankClosePage = true
     
     override func createView() {
-        view.backgroundColor = .clear
+        view.backgroundColor = .black
         setNavBarViewHidden(true)
 
         contentView.addSubview(netWorkImageView)

+ 0 - 144
AIEmoji/Common/GlobalImports/GlobalImports.swift

@@ -5,148 +5,4 @@
 //  Created by 100Years on 2024/12/20.
 //
 
-//@_exported import Foundation
-//@_exported import UIKit
-//@_exported import SnapKit
 @_exported import TSSmalCoacopods
-import AVFoundation
-
-func k_Height_statusBar() -> CGFloat {
-    var statusBarHeight: CGFloat = 0;
-    if #available(iOS 13.0, *) {
-        let scene = UIApplication.shared.connectedScenes.first;
-        guard let windowScene = scene as? UIWindowScene else {return 0};
-        guard let statusBarManager = windowScene.statusBarManager else {return 0};
-        statusBarHeight = statusBarManager.statusBarFrame.height;
-    } else {
-        statusBarHeight = UIApplication.shared.statusBarFrame.height;
-    }
-    return statusBarHeight;
-}
-/// ②、顶部安全区高度 k_Height_safeAreaInsetsTop
-func k_Height_safeAreaInsetsTop() -> CGFloat {
-    if #available(iOS 13.0, *) {
-        let scene = UIApplication.shared.connectedScenes.first;
-        guard let windowScene = scene as? UIWindowScene else {return 0}; // guard:如果 expression 值计算为false,则执行代码块内的 guard 语句。(必须包含一个控制语句: return、 break、 continue 或 throw。)。as?:类型转换,(还有这两种:as、as!)
-        guard let window = windowScene.windows.first else {return 0};
-        return window.safeAreaInsets.top;
-    } else if #available(iOS 11.0, *) {
-        guard let window = UIApplication.shared.windows.first else {return 0};
-        return window.safeAreaInsets.top;
-    }
-    return 0;
-}
-
-/// ③、底部安全区高度
-func k_Height_safeAreaInsetsBottom() -> CGFloat {
-    if #available(iOS 13.0, *) {
-        let scene = UIApplication.shared.connectedScenes.first;
-        guard let windowScene = scene as? UIWindowScene else {return 0};
-        guard let window = windowScene.windows.first else {return 0};
-        return window.safeAreaInsets.bottom;
-    } else if #available(iOS 11.0, *) {
-        guard let window = UIApplication.shared.windows.first else {return 0};
-        return window.safeAreaInsets.bottom;
-    }
-    return 0;
-}
-/** 屏幕宽度 */
-let k_ScreenWidth = UIScreen.main.bounds.size.width
-/** 屏幕高度 */
-let k_ScreenHeight = UIScreen.main.bounds.size.height
-/* 导航栏高度 固定高度 = 44.0f */
-//let k_Height_NavContentBar :CGFloat  = UINavigationBar.appearance().frame.size.height
-let k_Height_NavBar :CGFloat = 44.0
-/** 状态栏高度 */
-let k_Height_StatusBar :CGFloat = k_Height_statusBar()
-/** 状态栏+导航栏的高度 */
-let k_Nav_Height: CGFloat = k_Height_NavBar + k_Height_StatusBar
-/** 底部tabBar栏高度(不包含安全区,即:在 iphoneX 之前的手机) */
-let k_TabBar_Height :CGFloat = 49.0
-/** 底部导航栏高度(包括安全区),一般使用这个值 */
-let k_Height_TabBar :CGFloat = k_Height_safeAreaInsetsBottom() + k_TabBar_Height
-
-
-
-// MARK: - 检查系统版本 -
-/* 检查系统版本 */
-/// 版本号相同:
-func systemVersionEqual(version:String) -> Bool {
-    return UIDevice.current.systemVersion == version
-}
-
-/// 系统版本高于等于该version  测试发现只能传入带一位小数点的版本号  不然会报错    具体原因待探究
-func systemVersionGreaterThan(version:String) -> Bool {
-    return UIDevice.current.systemVersion.compare(version, options: .numeric, range: version.startIndex..<version.endIndex, locale: Locale(identifier:version)) != ComparisonResult.orderedAscending
-}
-
-//判断是否是 x、及x以上 系列
-func isIphoneX() -> Bool {
-    return k_Height_safeAreaInsetsBottom() > 0.0; // 底部安全区 > 0 时,
-    
-}
-
-//设计稿为 375*812 比例,所以比例系数为 375.0/kScreenWidth
-let kDesignScale = k_ScreenWidth/375.0
-
-
-func kGetUIH(designSize:CGSize,currentW:CGFloat) -> CGFloat {
-    let scale = designSize.width/currentW
-    return designSize.height/scale
-}
-    
-
-public func debugPrint<T>(_ messsage: T, file: String = #file, funcName: String = #function, lineNum: Int = #line) {
-    #if DEBUG
-    let fileName = (file as NSString).lastPathComponent
-    print(Date.hmsString + " \(fileName) (\(funcName)): [\(lineNum)]- \(messsage)")
-    #endif
-}
-
-public func dePrint<T>(_ messsage: T) {
-    #if DEBUG
-    print(Date.hmsString + " \(messsage)")
-    #endif
-}
-
-public func className(from object: Any) -> String {
-    return String(describing: type(of: object))
-}
-
-/// 震动
-func playVibration() {
-    // 默认震动   kSystemSoundID_Vibrate
-    // 1519     短震 3D Touch中的peek震动反馈
-    // 1520     短震 3D Touch中的pop震动反馈
-    // 1521     连续三次短震动
-    // peek的震动反馈轻于pop
-    
-    let soundID: UInt32 = 1520
-    AudioServicesPlaySystemSound(soundID)
-}
-
-func kPresentModalVC(target:UIViewController,
-                     modelVC:UIViewController,
-                       style:UIModalPresentationStyle = .overFullScreen,
-             transitionStyle:UIModalTransitionStyle = .coverVertical,
-                  completion: (() -> Void)? = nil){
-    let navi = TSBaseNavigationC(rootViewController: modelVC)
-    navi.modalPresentationStyle = style
-    navi.modalTransitionStyle = transitionStyle
-    target.present(navi, animated: true,completion: completion)
-}
-
-func kPushVC(target:UIViewController,modelVC:UIViewController){
-    modelVC.hidesBottomBarWhenPushed = true
-    target.navigationController?.pushViewController(modelVC, animated: true)
-}
-
-
-//public extension String {
-//    
-//    var localized:String {
-//        return NSLocalizedString(self, comment: self)
-////        return self
-//    }
-//    
-//}

+ 28 - 0
AIEmoji/Common/NetworkManager/TSNetWork/TSNetWork+Business.swift

@@ -28,6 +28,7 @@ enum TSNeURLType:String {
     case imageRestore = "/api/image/restore"          //老照片修复
     case eyeOpen = "/api/image/eye-open"          //睁眼
     case pretty = "/api/image/pretty"          //美容
+    case photoAnimation = "/api/image/animation"          //照片变活
     
     
     func getUrlString() -> String {
@@ -114,6 +115,33 @@ extension TSNetworkManager {
         },completion: completion)
         return request
     }
+    
+    func downloadFile(
+        urlString: String,
+        to destination: URL,
+        progressHandler: ((Double) -> Void)? = nil,
+        completion: @escaping (URL?, Error?) -> Void
+    ) -> DownloadRequest? {
+        let request = self.downloadFile(
+            urlString: urlString,
+            to: destination,
+            progressHandler: { progress in
+                print("下载进度: \(progress * 100)%")
+                progressHandler?(progress)
+            },
+            completion: { result in
+                switch result {
+                case .success(let fileURL):
+                    dePrint("下载完成,文件保存在: \(fileURL.path)")
+                    completion(fileURL,nil)
+                case .failure(let error):
+                    dePrint("下载失败: \(error.localizedDescription)")
+                    completion(nil,error)
+                }
+            }
+        )
+        return request
+    }
 
 }
 

+ 65 - 0
AIEmoji/Common/NetworkManager/TSNetWork/TSNetworkManager.swift

@@ -204,6 +204,71 @@ extension TSNetworkManager {
         
         return request
     }
+    
+    
+    /// 下载文件
+    /// - Parameters:
+    ///   - url: 下载URL
+    ///   - destination: 目标保存路径 (可选,不传则使用临时目录)
+    ///   - progressHandler: 进度回调 (0.0~1.0)
+    ///   - completion: 完成回调 (返回文件URL或错误)
+    func downloadFile(
+        urlString: String,
+        to destination: URL? = nil,
+        progressHandler: ((Double) -> Void)? = nil,
+        completion: @escaping (Result<URL, Error>) -> Void
+    ) -> DownloadRequest? {
+        
+        
+        guard let url = URL(string: urlString) else {
+            completion(.failure(NSError(domain: "url nil", code: 0)))
+            return nil
+        }
+        
+        // 设置下载目标路径
+        let destination: DownloadRequest.Destination = { temporaryURL, response in
+            // 如果用户指定了目标路径
+            if let destination = destination {
+                // 确保目录存在
+                let directory = destination.deletingLastPathComponent()
+                try? FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true, attributes: nil)
+                return (destination, [.removePreviousFile, .createIntermediateDirectories])
+            }
+            
+            // 否则使用临时目录
+            let documentsURL = FileManager.default.temporaryDirectory
+            let suggestedFilename = response.suggestedFilename ?? url.lastPathComponent
+            let fileURL = documentsURL.appendingPathComponent(suggestedFilename)
+            
+            return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
+        }
+        
+        // 开始下载
+        let request = AF.download(url, to: destination)
+            .downloadProgress { progress in
+                // 主线程回调进度
+                DispatchQueue.main.async {
+                    progressHandler?(progress.fractionCompleted)
+                }
+            }
+            .response { response in
+                // 主线程回调结果
+                DispatchQueue.main.async {
+                    switch response.result {
+                    case .success(let fileURL):
+                        if let fileURL = fileURL {
+                            completion(.success(fileURL))
+                        } else {
+                            completion(.failure(NSError(domain: "DownloadError", code: -1, userInfo: [NSLocalizedDescriptionKey: "文件路径无效"])))
+                        }
+                    case .failure(let error):
+                        completion(.failure(error))
+                    }
+                }
+            }
+        
+        return request
+    }
 
 }
 

+ 3 - 3
AIEmoji/Common/Purchase/TSPurchaseManager.swift

@@ -143,9 +143,9 @@ public class PurchaseManager: NSObject {
     }
 
     @objc public var isVip: Bool {
-//        #if DEBUG
-//            return true
-//        #endif
+        #if DEBUG
+            return true
+        #endif
         guard let expiresDate = expiredDate else {
             return false
         }

+ 39 - 0
AIEmoji/Common/Tool/PhotoManager.swift

@@ -133,6 +133,34 @@ class PhotoManager {
             }
         }
     }
+    
+    /// 保存图片到相册
+    /// - Parameters:
+    ///   - image: 要保存的 UIImage
+    ///   - completion: 保存结果的回调,返回成功与否和错误信息
+    func saveVideoToAlbum(videoURL:URL, completion: @escaping (Bool, Error?) -> Void) {
+        // 检查相册权限
+        PHPhotoLibrary.requestAuthorization { status in
+            DispatchQueue.main.async {
+                switch status {
+                case .authorized:
+                    // 权限已授权,保存图片
+                    self.save(videoURL: videoURL, completion: completion)
+                case .limited:
+                    // 在受限权限下保存图片
+                    self.save(videoURL: videoURL, completion: completion)
+                case .denied, .restricted:
+                    // 权限被拒绝或受限
+                    completion(false, NSError(domain: "PhotoSaver", code: 1, userInfo: [NSLocalizedDescriptionKey: "Photo Library access is denied."]))
+                case .notDetermined:
+                    // 不会进入这个分支,因为已经请求了权限
+                    completion(false, NSError(domain: "PhotoSaver", code: 2, userInfo: [NSLocalizedDescriptionKey: "Photo Library access not determined."]))
+                @unknown default:
+                    completion(false, NSError(domain: "PhotoSaver", code: 3, userInfo: [NSLocalizedDescriptionKey: "Unknown authorization status."]))
+                }
+            }
+        }
+    }
 
     /// 保存图片到相册的具体实现
     private func save(image: UIImage, completion: @escaping (Bool, Error?) -> Void) {
@@ -144,4 +172,15 @@ class PhotoManager {
             }
         }
     }
+    
+    /// 保存视频到相册的具体实现
+    private func save(videoURL:URL, completion: @escaping (Bool, Error?) -> Void) {
+        PHPhotoLibrary.shared().performChanges({
+            PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoURL)
+        }) { success, error in
+            DispatchQueue.main.async {
+                completion(success, error)
+            }
+        }
+    }
 }

+ 198 - 0
AIEmoji/Common/Tool/TSBusinessFileManager.swift

@@ -0,0 +1,198 @@
+//
+//  TSBusinessFileManager.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/27.
+//
+
+class TSBusinessFileManager {
+    
+    /// 获取 Video 下载后保存的的文件件路径
+    static var saveVideoPathURL:URL = {
+        let saveRingPathURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("video")
+        return saveRingPathURL
+    }()
+    
+    static var saveCacheAllPathURL:URL = {
+        let saveRingPathURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("cacheAll")
+        return saveRingPathURL
+    }()
+    
+    public static func generateFileName(
+        urlString: String,
+        fileEx:String? = nil,
+    missingEx:String? = nil
+    )->String?{
+        guard let url = URL(string: urlString) else{
+            return nil
+        }
+        
+        var fileName = url.path.md5
+
+        // 使用 URL 的 MD5 哈希值作为缓存文件名,附加 URL 的后缀名
+        var fileExtension = ""
+        if let fileEx = fileEx {
+            fileExtension = fileEx
+        }else{
+            var missingExStr = ""
+            if let missingEx = missingEx {
+                missingExStr = missingEx
+            }
+            fileExtension = url.pathExtension.isEmpty ? missingExStr : url.pathExtension
+        }
+
+        if fileExtension.count > 0 {
+            fileName = url.path.md5 + ".\(fileExtension)"
+        }
+         
+        return fileName
+    }
+    
+    //获取 urlstring 本地的缓存 url path
+    public static func getLocalURL(
+        urlString: String,
+        fileEx:String? = nil,
+    missingEx:String? = nil)->URL?{
+        
+        guard let fileName = generateFileName(urlString: urlString,fileEx:fileEx,missingEx: missingEx) else{
+            return nil
+        }
+        
+        //检查文件是否已存在于缓存中
+        let ringFileURL = saveVideoPathURL.appendingPathComponent(fileName)
+        if FileManager.default.fileExists(atPath: ringFileURL.path) {
+            print("文件已存在于缓存中: \(ringFileURL)")
+            return ringFileURL
+        }
+        
+        let cachedFileURL = saveCacheAllPathURL.appendingPathComponent(fileName)
+        if FileManager.default.fileExists(atPath: cachedFileURL.path) {
+            print("文件已存在于缓存中: \(cachedFileURL)")
+            return cachedFileURL
+        }
+        
+        return nil
+    }
+    
+
+}
+
+//缓存路径
+extension TSBusinessFileManager {
+    
+    //检查 url 对不对
+    public static func generateLocalURL(
+        from urlString: String,
+        fileEx:String? = nil,
+        missingEx:String? = nil,
+        frontPathURL:URL,
+        completion:((String?, Error?) -> Void)? = nil
+    )->URL?
+    {
+        guard let url = URL(string: urlString) else{
+            completion?(nil, NSError(domain: "url null", code: 0))
+            return nil
+        }
+        
+        if !urlString.contains("http") && urlString.contains("/"){
+            completion?(urlString.fillCachePath, nil)
+            return nil
+        }
+        
+        let fileManager = FileManager.default
+        let cacheAllDirectory = frontPathURL
+        
+        // 创建 `cacheAll` 文件夹(如果不存在)
+        if !fileManager.fileExists(atPath: cacheAllDirectory.path) {
+            do {
+                try fileManager.createDirectory(at: cacheAllDirectory, withIntermediateDirectories: true, attributes: nil)
+            } catch {
+                completion?(nil, error)
+                return nil
+            }
+        }
+        
+        
+        guard let fileName = generateFileName(urlString: urlString,fileEx:fileEx,missingEx: missingEx) else{
+            completion?(nil, NSError(domain: "url error", code: 0))
+            return nil
+        }
+        
+        let cachedFileURL = cacheAllDirectory.appendingPathComponent(fileName)
+        return cachedFileURL
+    }
+    
+}
+
+//缓存路径
+extension TSBusinessFileManager {
+    
+    //检查 url 对不对
+    public static func generateCachesURL(
+        from urlString: String,
+        fileEx:String? = nil,
+    missingEx:String? = nil,
+        cacheDirectory:String = "cacheAll",
+        completion:((String?, Error?) -> Void)? = nil
+    )->URL?
+    {
+        guard let url = URL(string: urlString) else{
+            completion?(nil, NSError(domain: "url null", code: 0))
+            return nil
+        }
+        
+        if !urlString.contains("http") && urlString.contains("/"){
+            completion?(urlString.fillCachePath, nil)
+            return nil
+        }
+        
+        let fileManager = FileManager.default
+        
+        // 获取缓存目录下的 `cacheAll` 文件夹路径
+        let cachesDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
+        let cacheAllDirectory = cachesDirectory.appendingPathComponent(cacheDirectory)
+        
+        // 创建 `cacheAll` 文件夹(如果不存在)
+        if !fileManager.fileExists(atPath: cacheAllDirectory.path) {
+            do {
+                try fileManager.createDirectory(at: cacheAllDirectory, withIntermediateDirectories: true, attributes: nil)
+            } catch {
+                completion?(nil, error)
+                return nil
+            }
+        }
+        
+        
+        guard let fileName = generateFileName(urlString: urlString,fileEx:fileEx,missingEx: missingEx) else{
+            completion?(nil, NSError(domain: "url error", code: 0))
+            return nil
+        }
+
+        let cachedFileURL = cacheAllDirectory.appendingPathComponent(fileName)
+        return cachedFileURL
+    }
+    
+//    //获取 urlstring 本地的缓存 url path
+//    public static func getCachesURL(
+//        from urlString: String,
+//        fileEx:String? = nil,
+//    missingEx:String? = nil,
+//        cacheDirectory:String = "cacheAll")->URL?{
+//        
+//        if let cachedFileURL = generateCachesURL(
+//            from: urlString,
+//            fileEx: fileEx,
+//            missingEx: missingEx,
+//            cacheDirectory: cacheDirectory
+//        ){
+//            //检查文件是否已存在于缓存中
+//            if FileManager.default.fileExists(atPath: cachedFileURL.path) {
+//                print("文件已存在于缓存中: \(cachedFileURL)")
+//                return cachedFileURL
+//            }
+//        }
+//        
+//        return nil
+//    }
+    
+}

+ 46 - 0
AIEmoji/Common/Tool/TSDownloadManager.swift

@@ -0,0 +1,46 @@
+//
+//  TSPublicContent.swift
+//  AIRingtone
+//
+//  Created by 100Years on 2025/3/20.
+//
+
+
+import AVFoundation
+import Alamofire
+
+class TSDownloadManager {
+    
+    static func getDownLoadVideo(urlString:String,progressHandler: ((Double) -> Void)? = nil,complete:@escaping (URL?,Bool)->Void){
+        if let path = TSBusinessFileManager.getLocalURL(urlString: urlString,fileEx:nil,missingEx: "mp4") {
+            complete(path,false)
+        }else{
+            _ = TSDownloadManager.downloadVideo(urlString:urlString,missingEx: "mp4",progressHandler: progressHandler) { url, error in
+                if let path = url {
+                    complete(path,true)
+                }else{
+                    complete(nil,true)
+                }
+            }
+        }
+    }
+    
+    static func downloadVideo(
+        urlString: String,
+        fileEx:String? = nil,
+    missingEx:String? = nil,
+        progressHandler: ((Double) -> Void)? = nil,
+        completion: @escaping (URL?, Error?) -> Void
+    ) -> DownloadRequest? {
+        
+        if let fileName = TSBusinessFileManager.getLocalURL(urlString: urlString,fileEx:fileEx,missingEx:missingEx){
+            completion(fileName,nil)
+            return nil
+        }
+        guard let savePath = TSBusinessFileManager.generateLocalURL(from: urlString, fileEx: fileEx, missingEx: missingEx,frontPathURL: TSBusinessFileManager.saveVideoPathURL,completion: { string, error in
+            completion(nil,error)
+        })else { return nil }
+        
+        return TSNetworkShared.downloadFile(urlString: urlString,to: savePath, progressHandler:progressHandler,completion: completion)
+    }
+}

+ 0 - 157
AIEmoji/Common/Tool/TSFileManagerTool.swift

@@ -1,157 +0,0 @@
-//
-//  TSFileManagerTool.swift
-//  TSLiveWallpaper
-//
-//  Created by 100Years on 2024/12/26.
-//
-
-class TSFileManagerTool {
-    
-    /// 获取 Video 下载后保存的的文件件路径
-    static var saveDownVideoPathURL:URL = {
-        let saveVideoPathURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("livePhoto").appendingPathComponent("saveVideo")
-        return saveVideoPathURL
-    }()
-    
-    /// 获取 Video 临时编辑的文件件路径
-    static var editLiveVideoPathURL:URL = {
-        let editVideoPathURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("livePhoto").appendingPathComponent("editVideo")
-        return editVideoPathURL
-    }()
-    
-    /// 获取 Video 编辑后保存的的文件件路径
-    static var saveLiveVideoPathURL:URL = {
-        let saveVideoPathURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("livePhoto").appendingPathComponent("saveVideo")
-        return saveVideoPathURL
-    }()
-    
-
-    /// 获取沙盒 Documents 目录路径
-    static var documentsDirectory: URL {
-        return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
-    }
-
-    /// 获取沙盒 Cache 目录路径
-    static var cacheDirectory: URL {
-        return FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
-    }
-
-    /// 获取沙盒 Temporary 目录路径
-    static var temporaryDirectory: URL {
-        return FileManager.default.temporaryDirectory
-    }
-
-    static func copyFileWithOverwrite(from sourceURL: URL, to targetURL: URL) {
-        let fileManager = FileManager.default
-        do {
-            removeItem(from: targetURL)
-            checkFolderAndCreate(from: targetURL)
-            try fileManager.copyItem(at: sourceURL, to: targetURL)
-            debugPrint("文件复制成功!")
-        } catch {
-            debugPrint("文件复制失败: \(error.localizedDescription)")
-        }
-    }
-    
-    static func removeItem(from sourceURL: URL) {
-        let fileManager = FileManager.default
-        do {
-            // 如果目标路径存在同名文件,先删除旧文件
-            if fileManager.fileExists(atPath: sourceURL.path) {
-                try fileManager.removeItem(at: sourceURL)
-            }
-            debugPrint("文件删除成功!")
-        } catch {
-            debugPrint("文件删除失败: \(error.localizedDescription)")
-        }
-    }
-    
-    /// 移动文件的方法(自动创建目标文件夹)
-    /// - Parameters:
-    ///   - sourceURL: 文件的源 URL
-    ///   - destinationURL: 目标 URL
-    /// - Throws: 如果移动失败,会抛出错误
-    static func moveFile(from sourceURL: URL, to destinationURL: URL) {
-        let fileManager = FileManager.default
-        
-        // 检查源文件是否存在
-        guard fileManager.fileExists(atPath: sourceURL.path) else {
-            let error = NSError(domain: "FileMoveError", code: 404, userInfo: [NSLocalizedDescriptionKey: "源文件不存在"])
-            debugPrint(error)
-            return
-        }
-        
-        // 获取目标文件夹的路径
-        let destinationDirectory = destinationURL.deletingLastPathComponent()
-        do {
-            // 如果目标文件夹不存在,创建文件夹
-            if !fileManager.fileExists(atPath: destinationDirectory.path) {
-                try fileManager.createDirectory(at: destinationDirectory, withIntermediateDirectories: true, attributes: nil)
-            }
-            
-            // 检查目标路径是否已经存在文件
-            if fileManager.fileExists(atPath: destinationURL.path) {
-                // 如果需要覆盖,可以选择先删除目标文件
-                try fileManager.removeItem(at: destinationURL)
-            }
-            
-            // 尝试移动文件
-            try fileManager.moveItem(at: sourceURL, to: destinationURL)
-        } catch {
-            debugPrint("尝试移动文件失败: \(error.localizedDescription)")
-        }
-    }
-    
-    static func getFileName(from url: URL, includeExtension: Bool = true) -> String {
-        if includeExtension {
-            return url.lastPathComponent
-        } else {
-            return url.deletingPathExtension().lastPathComponent
-        }
-    }
-    
-    static func checkFolderAndCreate(from destinationURL: URL){
-        let fileManager = FileManager.default
-        let destinationDirectory = destinationURL.deletingLastPathComponent()
-        // 如果目标文件夹不存在,创建文件夹
-        if !fileManager.fileExists(atPath: destinationDirectory.path) {
-            do {
-                try fileManager.createDirectory(at: destinationDirectory, withIntermediateDirectories: true, attributes: nil)
-            } catch {
-                debugPrint("尝试创建文件夹失败: \(error.localizedDescription)")
-            }
-        }
-    }
-
-    // MARK: - 文件操作方法
-
-    /// 检查文件或文件夹是否存在
-    static func fileExists(at url: URL) -> Bool {
-        return FileManager.default.fileExists(atPath: url.path)
-    }
-
-    /// 创建文件夹
-    static func createDirectory(at url: URL) throws {
-        if !fileExists(at: url) {
-            try FileManager.default.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
-        }
-    }
-    
-    //获取缓存目录下文件夹路径
-    static func getCacheSubPath(at url: URL) ->String? {
-        let array = url.path.components(separatedBy:"/Caches/")
-        let cashFilePath = array.last
-        return cashFilePath
-    }
-    
-}
-
-extension String {
-    var fillCachePath:String{
-        return TSFileManagerTool.cacheDirectory.appendingPathComponent(self).path
-    }
-    
-    var fillCacheURL:URL{
-        return TSFileManagerTool.cacheDirectory.appendingPathComponent(self)
-    }
-}