From aaf5051eeb6a54403fa93c043306bb960ef0dc7a Mon Sep 17 00:00:00 2001 From: leminlimez <59540996+leminlimez@users.noreply.github.com> Date: Sun, 8 Jun 2025 18:58:46 -0400 Subject: [PATCH] video wallpapers --- Pocket Poster.xcodeproj/project.pbxproj | 11 +- .../Controllers/PosterBoardManager.swift | 11 +- Pocket Poster/Controllers/VideoHandler.swift | 224 ++++++++++++++++++ ...e.posterkit.provider.descriptor.identifier | 1 + .../com.apple.posterkit.role.identifier | 1 + .../Resources/VideoCAML/providerInfo.plist | Bin 0 -> 410 bytes .../index.xml | 58 +++++ .../main.caml | 40 ++++ .../Wallpaper.plist | 46 ++++ ...apple.posterkit.provider.contents.userInfo | 14 ++ Pocket Poster/Views/ContentView.swift | 80 ++++++- 11 files changed, 479 insertions(+), 7 deletions(-) create mode 100644 Pocket Poster/Controllers/VideoHandler.swift create mode 100644 Pocket Poster/Resources/VideoCAML/com.apple.posterkit.provider.descriptor.identifier create mode 100644 Pocket Poster/Resources/VideoCAML/com.apple.posterkit.role.identifier create mode 100644 Pocket Poster/Resources/VideoCAML/providerInfo.plist create mode 100644 Pocket Poster/Resources/VideoCAML/versions/1/contents/9183.Custom-810w-1080h@2x~ipad.wallpaper/9183.Custom_Floating-810w-1080h@2x~ipad.ca/index.xml create mode 100644 Pocket Poster/Resources/VideoCAML/versions/1/contents/9183.Custom-810w-1080h@2x~ipad.wallpaper/9183.Custom_Floating-810w-1080h@2x~ipad.ca/main.caml create mode 100644 Pocket Poster/Resources/VideoCAML/versions/1/contents/9183.Custom-810w-1080h@2x~ipad.wallpaper/Wallpaper.plist create mode 100644 Pocket Poster/Resources/VideoCAML/versions/1/contents/com.apple.posterkit.provider.contents.userInfo diff --git a/Pocket Poster.xcodeproj/project.pbxproj b/Pocket Poster.xcodeproj/project.pbxproj index 5c9db5d..7413994 100644 --- a/Pocket Poster.xcodeproj/project.pbxproj +++ b/Pocket Poster.xcodeproj/project.pbxproj @@ -17,6 +17,9 @@ /* Begin PBXFileSystemSynchronizedRootGroup section */ 6F09B79F2DEB694B00CDE89C /* Pocket Poster */ = { isa = PBXFileSystemSynchronizedRootGroup; + explicitFolders = ( + Resources/VideoCAML, + ); path = "Pocket Poster"; sourceTree = ""; }; @@ -261,7 +264,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Pocket Poster/Preview Content\""; DEVELOPMENT_TEAM = 2P4CP47H76; ENABLE_PREVIEWS = YES; @@ -278,7 +281,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 2.0; PRODUCT_BUNDLE_IDENTIFIER = "com.lemin.Pocket-Poster"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; @@ -293,7 +296,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Pocket Poster/Preview Content\""; DEVELOPMENT_TEAM = 2P4CP47H76; ENABLE_PREVIEWS = YES; @@ -310,7 +313,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 2.0; PRODUCT_BUNDLE_IDENTIFIER = "com.lemin.Pocket-Poster"; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/Pocket Poster/Controllers/PosterBoardManager.swift b/Pocket Poster/Controllers/PosterBoardManager.swift index 704980a..87b283d 100644 --- a/Pocket Poster/Controllers/PosterBoardManager.swift +++ b/Pocket Poster/Controllers/PosterBoardManager.swift @@ -161,9 +161,18 @@ class PosterBoardManager: ObservableObject { } } - func applyTendies(appHash: String) throws { + func applyTendies(appHash: String, videoURL: URL?) throws { // organize the descriptors into their respective extensions var extList: [String: [URL]] = [:] + // create the video first + if let videoURL = videoURL { + do { + let newVideo = try VideoHandler.createCaml(from: videoURL, autoReverses: false) + extList["com.apple.WallpaperKit.CollectionsPoster"] = [newVideo] + } catch { + print(error.localizedDescription) + } + } for url in selectedTendies { let unzippedDir = try unzipFile(at: url) guard let descriptors = try getDescriptorsFromTendie(unzippedDir) else { continue } // TODO: Add error handling diff --git a/Pocket Poster/Controllers/VideoHandler.swift b/Pocket Poster/Controllers/VideoHandler.swift new file mode 100644 index 0000000..26b03b0 --- /dev/null +++ b/Pocket Poster/Controllers/VideoHandler.swift @@ -0,0 +1,224 @@ +// +// VideoHandler.swift +// Pocket Poster +// +// Created by lemin on 6/8/25. +// + +import Foundation +import AVFoundation +import UIKit + +extension CGImage { + var png: Data? { + guard let mutableData = CFDataCreateMutable(nil, 0), + let destination = CGImageDestinationCreateWithData(mutableData, "public.png" as CFString, 1, nil) else { return nil } + CGImageDestinationAddImage(destination, self, nil) + guard CGImageDestinationFinalize(destination) else { return nil } + return mutableData as Data + } + var jpg: Data? { + let uiimg = UIImage(cgImage: self) + return uiimg.jpegData(compressionQuality: 0.7) + } +} + +class VideoHandler { + static func createCaml(from url: URL, autoReverses: Bool) throws -> URL { + // copy the resources + let descrURL = SymHandler.getDocumentsDirectory().appendingPathComponent(UUID().uuidString, conformingTo: .directory) + try FileManager.default.createDirectory(at: descrURL, withIntermediateDirectories: false) + + print("pre resource") + guard let rc = Bundle.main.url(forResource: "VideoCAML", withExtension: nil) else { throw URLError(.fileDoesNotExist) } + print("post resource") + try FileManager.default.copyItem(at: rc, to: descrURL.appendingPathComponent("videoCAML")) + print("post copy") + + let coreAnimDir = descrURL.appending(path: "videoCAML/versions/1/contents/9183.Custom-810w-1080h@2x~ipad.wallpaper/9183.Custom_Background-810w-1080h@2x~ipad.ca") + let assetsFolder = coreAnimDir.appendingPathComponent("assets", conformingTo: .directory) + if !FileManager.default.fileExists(atPath: assetsFolder.path()) { + try FileManager.default.createDirectory(at: assetsFolder, withIntermediateDirectories: true) + } + + // load the video + let asset = AVURLAsset(url: url) + + let assetImageGenerator = AVAssetImageGenerator(asset: asset) + assetImageGenerator.appliesPreferredTrackTransform = true + assetImageGenerator.apertureMode = AVAssetImageGenerator.ApertureMode.encodedPixels + + // create the caml + guard let track = asset.tracks(withMediaType: .video).first else { + print("Failed to get video track") + throw AVError(.decodeFailed) + } + let size = track.naturalSize.applying(track.preferredTransform) + let width = Int(size.width) + let height = Int(size.height) + let fps = track.nominalFrameRate + let duration = CMTimeGetSeconds(asset.duration) + let totalFrames = Int(fps * Float(duration)) + let animDur = Double(totalFrames) / Double(fps) + var camlData = """ + + + + + +""" + // write the caml + guard let caData = camlData.data(using: .utf8) else { throw URLError(.unknown) } + try caData.write(to: coreAnimDir.appendingPathComponent("main.caml")) + + // write the other info xml + camlData = """ + + + + + assetManifest + assetManifest.caml + documentHeight + \(height) + documentResizesToView + + documentWidth + \(width) + dynamicGuidesEnabled + + geometryFlipped + + guidesEnabled + + interactiveMouseEventsEnabled + + interactiveShowsCursor + + interactiveTouchEventsEnabled + + loopEnd + 0.0 + loopStart + 0.0 + loopingEnabled + + multitouchDisablesMouse + + multitouchEnabled + + presentationMouseEventsEnabled + + presentationShowsCursor + + presentationTouchEventsEnabled + + rootDocument + main.caml + savesWindowFrame + + scalesToFitInPlayer + + showsTouches + + snappingEnabled + + timelineMarkers + [(null)] + touchesColor + 1 1 0 0.8 + unitsInPixelsInPlayer + + + + """ + + guard let xmlData = camlData.data(using: .utf8) else { throw URLError(.unknown) } + try xmlData.write(to: coreAnimDir.appendingPathComponent("index.xml", conformingTo: .xml)) + + return descrURL + } +} diff --git a/Pocket Poster/Resources/VideoCAML/com.apple.posterkit.provider.descriptor.identifier b/Pocket Poster/Resources/VideoCAML/com.apple.posterkit.provider.descriptor.identifier new file mode 100644 index 0000000..5018e06 --- /dev/null +++ b/Pocket Poster/Resources/VideoCAML/com.apple.posterkit.provider.descriptor.identifier @@ -0,0 +1 @@ +9183 \ No newline at end of file diff --git a/Pocket Poster/Resources/VideoCAML/com.apple.posterkit.role.identifier b/Pocket Poster/Resources/VideoCAML/com.apple.posterkit.role.identifier new file mode 100644 index 0000000..b92284b --- /dev/null +++ b/Pocket Poster/Resources/VideoCAML/com.apple.posterkit.role.identifier @@ -0,0 +1 @@ +PRPosterRoleLockScreen \ No newline at end of file diff --git a/Pocket Poster/Resources/VideoCAML/providerInfo.plist b/Pocket Poster/Resources/VideoCAML/providerInfo.plist new file mode 100644 index 0000000000000000000000000000000000000000..81447ade9ddc75457c21e8d348003b973b2fd6d5 GIT binary patch literal 410 zcmYc)$jK}&F)+Bo$i&RT%ErzWp;DGwRGgWg7pan1l$?ed zc4}pD6p)6R6Q+`!lUQ85P_Th%p>P9p18cm1Oty1=URq{)X;ESc&;>q;#U-J|sV<2n zsX(hPiStWHg3Kw&%uQ8xJa&M^<4UPW1Is088Ckg~h+%n&xv3ElW@>SmUoc4PB3bze zzhHl`=~ literal 0 HcmV?d00001 diff --git a/Pocket Poster/Resources/VideoCAML/versions/1/contents/9183.Custom-810w-1080h@2x~ipad.wallpaper/9183.Custom_Floating-810w-1080h@2x~ipad.ca/index.xml b/Pocket Poster/Resources/VideoCAML/versions/1/contents/9183.Custom-810w-1080h@2x~ipad.wallpaper/9183.Custom_Floating-810w-1080h@2x~ipad.ca/index.xml new file mode 100644 index 0000000..5fa23a9 --- /dev/null +++ b/Pocket Poster/Resources/VideoCAML/versions/1/contents/9183.Custom-810w-1080h@2x~ipad.wallpaper/9183.Custom_Floating-810w-1080h@2x~ipad.ca/index.xml @@ -0,0 +1,58 @@ + + + + + assetManifest + assetManifest.caml + documentHeight + 3176 + documentResizesToView + + documentWidth + 3176 + dynamicGuidesEnabled + + geometryFlipped + + guidesEnabled + + interactiveMouseEventsEnabled + + interactiveShowsCursor + + interactiveTouchEventsEnabled + + loopEnd + 0.0 + loopStart + 0.0 + loopingEnabled + + multitouchDisablesMouse + + multitouchEnabled + + presentationMouseEventsEnabled + + presentationShowsCursor + + presentationTouchEventsEnabled + + rootDocument + main.caml + savesWindowFrame + + scalesToFitInPlayer + + showsTouches + + snappingEnabled + + timelineMarkers + [(null)] + touchesColor + 1 1 0 0.8 + unitsInPixelsInPlayer + + + diff --git a/Pocket Poster/Resources/VideoCAML/versions/1/contents/9183.Custom-810w-1080h@2x~ipad.wallpaper/9183.Custom_Floating-810w-1080h@2x~ipad.ca/main.caml b/Pocket Poster/Resources/VideoCAML/versions/1/contents/9183.Custom-810w-1080h@2x~ipad.wallpaper/9183.Custom_Floating-810w-1080h@2x~ipad.ca/main.caml new file mode 100644 index 0000000..bb97379 --- /dev/null +++ b/Pocket Poster/Resources/VideoCAML/versions/1/contents/9183.Custom-810w-1080h@2x~ipad.wallpaper/9183.Custom_Floating-810w-1080h@2x~ipad.ca/main.caml @@ -0,0 +1,40 @@ + + + + + diff --git a/Pocket Poster/Resources/VideoCAML/versions/1/contents/9183.Custom-810w-1080h@2x~ipad.wallpaper/Wallpaper.plist b/Pocket Poster/Resources/VideoCAML/versions/1/contents/9183.Custom-810w-1080h@2x~ipad.wallpaper/Wallpaper.plist new file mode 100644 index 0000000..54f1f1b --- /dev/null +++ b/Pocket Poster/Resources/VideoCAML/versions/1/contents/9183.Custom-810w-1080h@2x~ipad.wallpaper/Wallpaper.plist @@ -0,0 +1,46 @@ + + + + + appearanceAware + + assets + + lockAndHome + + default + + backgroundAnimationFileName + 9183.Custom_Background-810w-1080h@2x~ipad.ca + floatingAnimationFileNameKey + 9183.Custom_Floating-810w-1080h@2x~ipad.ca + identifier + 9183 + name + Chip + type + LayeredAnimation + + + + contentVersion + 2.01 + family + Chip + identifier + 9183 + logicalScreenClass + 810w-1080h@2x~ipad + name + Chip + preferredProminentColor + + dark + #00000 + default + #FFFFFF + + version + 1 + + diff --git a/Pocket Poster/Resources/VideoCAML/versions/1/contents/com.apple.posterkit.provider.contents.userInfo b/Pocket Poster/Resources/VideoCAML/versions/1/contents/com.apple.posterkit.provider.contents.userInfo new file mode 100644 index 0000000..f94f23a --- /dev/null +++ b/Pocket Poster/Resources/VideoCAML/versions/1/contents/com.apple.posterkit.provider.contents.userInfo @@ -0,0 +1,14 @@ + + + + + posterEnvironmentOverrides + + e30= + + wallpaperRepresentingFileName + 9183.Custom-810w-1080h@2x~ipad.wallpaper + wallpaperRepresentingIdentifier + 9999 + + diff --git a/Pocket Poster/Views/ContentView.swift b/Pocket Poster/Views/ContentView.swift index b89fd09..737536d 100644 --- a/Pocket Poster/Views/ContentView.swift +++ b/Pocket Poster/Views/ContentView.swift @@ -7,6 +7,7 @@ import SwiftUI import UniformTypeIdentifiers +import PhotosUI extension UIDocumentPickerViewController { @objc func fix_init(forOpeningContentTypes contentTypes: [UTType], asCopy: Bool) -> UIDocumentPickerViewController { @@ -14,6 +15,25 @@ extension UIDocumentPickerViewController { } } +struct Movie: Transferable { + let url: URL + + static var transferRepresentation: some TransferRepresentation { + FileRepresentation(contentType: .movie) { movie in + SentTransferredFile(movie.url) + } importing: { received in + let copy = URL.documentsDirectory.appending(path: "movie.mp4") + + if FileManager.default.fileExists(atPath: copy.path()) { + try FileManager.default.removeItem(at: copy) + } + + try FileManager.default.copyItem(at: received.file, to: copy) + return Self.init(url: copy) + } + } +} + struct ContentView: View { // Prefs @AppStorage("pbHash") var pbHash: String = "" @@ -22,6 +42,12 @@ struct ContentView: View { @State var showTendiesImporter: Bool = false + enum LoadState { + case unknown, loading, loaded(Movie), failed + } + @State var selectedVideo: PhotosPickerItem? + @State private var loadState = LoadState.unknown + @State var showErrorAlert = false @State var lastError: String? @State var hideResetHelp: Bool = true @@ -35,6 +61,49 @@ struct ContentView: View { } Section { + VStack { + switch loadState { + case .unknown: + EmptyView() + case .loading: + ProgressView() + case .loaded(let movie): + Text("Video imported") + case .failed: + Text("Import failed") + } + + switch loadState { + case .loaded(let movie): + Button(action: { + selectedVideo = nil + try? FileManager.default.removeItem(at: movie.url) + loadState = .unknown + }) { + Text("Remove Selected Video") + } + .buttonStyle(TintedButton(color: .red, fullwidth: true)) + default: + PhotosPicker("Select Video", selection: $selectedVideo, matching: .videos) + .buttonStyle(TintedButton(color: .yellow, fullwidth: true)) + .onChange(of: selectedVideo) { _ in + Task { + do { + loadState = .loading + + if let movie = try await selectedVideo?.loadTransferable(type: Movie.self) { + loadState = .loaded(movie) + } else { + loadState = .failed + } + } catch { + loadState = .failed + } + } + } + } + } + Button(action: { UIImpactFeedbackGenerator(style: .soft).impactOccurred() showTendiesImporter.toggle() @@ -62,14 +131,21 @@ struct ContentView: View { Text("Enter your PosterBoard app hash in Settings.") } else { VStack { - if !PosterBoardManager.shared.selectedTendies.isEmpty { + if !PosterBoardManager.shared.selectedTendies.isEmpty || selectedVideo != nil { Button(action: { UIImpactFeedbackGenerator(style: .soft).impactOccurred() UIApplication.shared.alert(title: NSLocalizedString("Applying Tendies...", comment: ""), body: NSLocalizedString("Please wait", comment: ""), animated: false, withButton: false) DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { do { - try PosterBoardManager.shared.applyTendies(appHash: pbHash) + var videoURL: URL? = nil + switch loadState { + case .loaded(let movie): + videoURL = movie.url + default: + videoURL = nil + } + try PosterBoardManager.shared.applyTendies(appHash: pbHash, videoURL: videoURL) PosterBoardManager.shared.selectedTendies.removeAll() SymHandler.cleanup() // just to be extra sure try? FileManager.default.removeItem(at: PosterBoardManager.shared.getTendiesStoreURL())