react-native-photo-video-editor 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,141 @@
1
+ import AVFoundation
1
2
  import Foundation
3
+ import Photos
4
+ import UIKit
2
5
 
3
- /// Resolves an editor source uri (file:// or a bare path) to a local
4
- /// filesystem path that ImageIO can read directly. Photos-library identifiers
5
- /// (ph://) and remote https:// sources are not yet supported.
6
+ /// Resolves an editor source URI (file://, bare path, ph://, or remote/data URI)
7
+ /// to a local filesystem path that ImageIO / AVFoundation can read directly.
6
8
  enum SourceResolver {
7
9
  static func resolvePath(sourceUri: String, tempPrefix: String) -> String? {
10
+ if sourceUri.hasPrefix("data:image/png;base64,") {
11
+ guard let separator = sourceUri.firstIndex(of: ","),
12
+ let data = Data(base64Encoded: String(sourceUri[sourceUri.index(after: separator)...])) else { return nil }
13
+ let file = FileManager.default.temporaryDirectory.appendingPathComponent("\(tempPrefix)_\(UUID().uuidString).png")
14
+ do {
15
+ try data.write(to: file)
16
+ return file.path
17
+ } catch { return nil }
18
+ }
8
19
  guard let url = URL(string: sourceUri) else { return nil }
9
20
  if url.isFileURL { return url.path }
10
21
  if url.scheme == nil { return sourceUri }
11
22
  return nil
23
+ let trimmed = sourceUri.trimmingCharacters(in: .whitespacesAndNewlines)
24
+ guard !trimmed.isEmpty else { return nil }
25
+
26
+ // 1. Direct filesystem path
27
+ if trimmed.hasPrefix("/") {
28
+ if FileManager.default.fileExists(atPath: trimmed) { return trimmed }
29
+ if let decoded = trimmed.removingPercentEncoding, FileManager.default.fileExists(atPath: decoded) { return decoded }
30
+ return trimmed
31
+ }
32
+
33
+ // 2. file:// URI
34
+ if trimmed.hasPrefix("file://") {
35
+ if let url = URL(string: trimmed), url.isFileURL {
36
+ return url.path
37
+ }
38
+ let stripped = String(trimmed.dropFirst(7))
39
+ if let decoded = stripped.removingPercentEncoding, FileManager.default.fileExists(atPath: decoded) {
40
+ return decoded
41
+ }
42
+ return stripped
43
+ }
44
+
45
+ // 3. ph:// Photo Library identifier (from react-native-image-picker or Photos)
46
+ if trimmed.hasPrefix("ph://") {
47
+ let localId = String(trimmed.dropFirst(5))
48
+ return resolvePHAsset(localIdentifier: localId, tempPrefix: tempPrefix)
49
+ }
50
+
51
+ // 4. assets-library:// legacy identifier
52
+ if trimmed.hasPrefix("assets-library://") {
53
+ if let url = URL(string: trimmed) {
54
+ let fetchResult = PHAsset.fetchAssets(withALAssetURLs: [url], options: nil)
55
+ if let asset = fetchResult.firstObject {
56
+ return exportPHAsset(asset, tempPrefix: tempPrefix)
57
+ }
58
+ }
59
+ }
60
+
61
+ // 5. Remote http:// or https:// URL
62
+ if trimmed.hasPrefix("http://") || trimmed.hasPrefix("https://") {
63
+ if let url = URL(string: trimmed) {
64
+ return downloadRemoteURL(url, tempPrefix: tempPrefix)
65
+ }
66
+ }
67
+
68
+ // 6. Base64 data: URI
69
+ if trimmed.hasPrefix("data:") {
70
+ return resolveDataURI(trimmed, tempPrefix: tempPrefix)
71
+ }
72
+
73
+ // 7. Fallback for un-schemed relative or encoded path
74
+ if let url = URL(string: trimmed), url.isFileURL {
75
+ return url.path
76
+ }
77
+ if let decoded = trimmed.removingPercentEncoding, FileManager.default.fileExists(atPath: decoded) {
78
+ return decoded
79
+ }
80
+ return trimmed
81
+ }
82
+
83
+ private static func resolvePHAsset(localIdentifier: String, tempPrefix: String) -> String? {
84
+ let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [localIdentifier], options: nil)
85
+ guard let asset = fetchResult.firstObject else { return nil }
86
+ return exportPHAsset(asset, tempPrefix: tempPrefix)
87
+ }
88
+
89
+ private static func exportPHAsset(_ asset: PHAsset, tempPrefix: String) -> String? {
90
+ let tempDir = FileManager.default.temporaryDirectory
91
+ if asset.mediaType == .video {
92
+ let semaphore = DispatchSemaphore(value: 0)
93
+ var resolvedPath: String?
94
+ let options = PHVideoRequestOptions()
95
+ options.isNetworkAccessAllowed = true
96
+ options.version = .current
97
+ options.deliveryMode = .highQualityFormat
98
+ PHImageManager.default().requestAVAsset(forVideo: asset, options: options) { avAsset, _, _ in
99
+ defer { semaphore.signal() }
100
+ if let urlAsset = avAsset as? AVURLAsset {
101
+ resolvedPath = urlAsset.url.path
102
+ }
103
+ }
104
+ _ = semaphore.wait(timeout: .now() + 5)
105
+ return resolvedPath
106
+ } else {
107
+ let options = PHImageRequestOptions()
108
+ options.isNetworkAccessAllowed = true
109
+ options.isSynchronous = true
110
+ options.deliveryMode = .highQualityFormat
111
+ var resolvedPath: String?
112
+ PHImageManager.default().requestImageDataAndOrientation(for: asset, options: options) { data, _, _, _ in
113
+ if let data {
114
+ let ext = (data.prefix(3) == Data([0xFF, 0xD8, 0xFF])) ? "jpg" : "png"
115
+ let outURL = tempDir.appendingPathComponent("\(tempPrefix)_\(UUID().uuidString).\(ext)")
116
+ try? data.write(to: outURL)
117
+ resolvedPath = outURL.path
118
+ }
119
+ }
120
+ return resolvedPath
121
+ }
122
+ }
123
+
124
+ private static func downloadRemoteURL(_ url: URL, tempPrefix: String) -> String? {
125
+ let ext = url.pathExtension.isEmpty ? "tmp" : url.pathExtension
126
+ let outURL = FileManager.default.temporaryDirectory.appendingPathComponent("\(tempPrefix)_\(UUID().uuidString).\(ext)")
127
+ guard let data = try? Data(contentsOf: url) else { return nil }
128
+ try? data.write(to: outURL)
129
+ return outURL.path
130
+ }
131
+
132
+ private static func resolveDataURI(_ dataUri: String, tempPrefix: String) -> String? {
133
+ guard let commaIndex = dataUri.firstIndex(of: ",") else { return nil }
134
+ let base64String = String(dataUri[dataUri.index(after: commaIndex)...])
135
+ guard let data = Data(base64Encoded: base64String, options: .ignoreUnknownCharacters) else { return nil }
136
+ let outURL = FileManager.default.temporaryDirectory.appendingPathComponent("\(tempPrefix)_\(UUID().uuidString).png")
137
+ try? data.write(to: outURL)
138
+ return outURL.path
12
139
  }
13
140
  }
141
+
@@ -36,8 +36,8 @@ enum PhotoExporter {
36
36
  throw PhotoExportError(code: "E_SOURCE_UNREADABLE", message: "The selected photo could not be decoded.")
37
37
  }
38
38
 
39
- let maxWidth = (exportOptions?["maxWidth"] as? NSNumber)?.doubleValue.map { CGFloat($0) }
40
- let maxHeight = (exportOptions?["maxHeight"] as? NSNumber)?.doubleValue.map { CGFloat($0) }
39
+ let maxWidth = (exportOptions?["maxWidth"] as? NSNumber).map { CGFloat($0.doubleValue) }
40
+ let maxHeight = (exportOptions?["maxHeight"] as? NSNumber).map { CGFloat($0.doubleValue) }
41
41
  let decodeTarget = max(maxWidth ?? defaultMaxDimension, maxHeight ?? defaultMaxDimension) * 2
42
42
 
43
43
  let thumbnailOptions: [CFString: Any] = [
@@ -1,3 +1,4 @@
1
+ import Accelerate
1
2
  import UIKit
2
3
 
3
4
  /// Applies the non-destructive `PhotoAdjustments` stack to a `UIImage`. The
@@ -32,62 +33,50 @@ enum PhotoAdjustmentRenderer {
32
33
  }
33
34
 
34
35
  private static func applyColorAdjustments(_ buffer: inout PixelBuffer, _ adjustments: PhotoAdjustments) {
35
- let exposureScale = pow(2, adjustments.exposure / 100)
36
- let brightnessOffset = adjustments.brightness / 100 * 80
37
- let contrastScale = (adjustments.contrast + 100) / 100
38
- let contrastTranslate = 128 * (1 - contrastScale)
39
- let saturationFactor = (adjustments.saturation + 100) / 100
40
- let tempShift = adjustments.temperature / 100 * 40
36
+ let exposureScale = Float(pow(2.0, adjustments.exposure / 100))
37
+ let brightnessOffset = Float(adjustments.brightness / 100 * 80)
38
+ let contrastScale = Float((adjustments.contrast + 100) / 100)
39
+ let contrastTranslate = Float(128 * (1 - contrastScale))
40
+ let saturationFactor = Float((adjustments.saturation + 100) / 100)
41
+ let tempShift = Float(adjustments.temperature / 100 * 40)
41
42
 
42
- for y in 0..<buffer.height {
43
- for x in 0..<buffer.width {
44
- let pixel = buffer[x, y]
45
- var r = Double(pixel.r) * exposureScale + brightnessOffset
46
- var g = Double(pixel.g) * exposureScale + brightnessOffset
47
- var b = Double(pixel.b) * exposureScale + brightnessOffset
48
- r = r * contrastScale + contrastTranslate
49
- g = g * contrastScale + contrastTranslate
50
- b = b * contrastScale + contrastTranslate
51
- let luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b
52
- r = luminance + (r - luminance) * saturationFactor
53
- g = luminance + (g - luminance) * saturationFactor
54
- b = luminance + (b - luminance) * saturationFactor
55
- r += tempShift
56
- b -= tempShift
57
- buffer[x, y] = (clampByte(r), clampByte(g), clampByte(b), pixel.a)
43
+ let combScale = exposureScale * contrastScale
44
+ let combOffset = brightnessOffset * contrastScale + contrastTranslate
45
+
46
+ buffer.mutateBytes { ptr, _, width, height in
47
+ let totalPixels = width * height
48
+ for i in 0..<totalPixels {
49
+ let offset = i * 4
50
+ let rVal = Float(ptr[offset])
51
+ let gVal = Float(ptr[offset + 1])
52
+ let bVal = Float(ptr[offset + 2])
53
+ let rBase = rVal * combScale + combOffset
54
+ let gBase = gVal * combScale + combOffset
55
+ let bBase = bVal * combScale + combOffset
56
+ let luminance = 0.2126 * rBase + 0.7152 * gBase + 0.0722 * bBase
57
+ let r = luminance + (rBase - luminance) * saturationFactor + tempShift
58
+ let g = luminance + (gBase - luminance) * saturationFactor
59
+ let b = luminance + (bBase - luminance) * saturationFactor - tempShift
60
+ ptr[offset] = UInt8(min(max(r, 0), 255))
61
+ ptr[offset + 1] = UInt8(min(max(g, 0), 255))
62
+ ptr[offset + 2] = UInt8(min(max(b, 0), 255))
58
63
  }
59
64
  }
60
65
  }
61
66
 
62
- /// Approximate Gaussian blur via three passes of separable box blur.
67
+ /// Hardware-accelerated box blur using Apple Accelerate vImage.
63
68
  private static func applyBoxBlur(_ buffer: inout PixelBuffer, radius: Int) {
64
69
  guard radius > 0 else { return }
65
- for _ in 0..<3 {
66
- boxBlurPass(&buffer, radius: radius, horizontal: true)
67
- boxBlurPass(&buffer, radius: radius, horizontal: false)
68
- }
69
- }
70
-
71
- private static func boxBlurPass(_ buffer: inout PixelBuffer, radius: Int, horizontal: Bool) {
72
- let source = buffer
73
- let outerLimit = horizontal ? buffer.height : buffer.width
74
- let innerLimit = horizontal ? buffer.width : buffer.height
75
- for outer in 0..<outerLimit {
76
- for inner in 0..<innerLimit {
77
- var r = 0
78
- var g = 0
79
- var b = 0
80
- var a = 0
81
- var count = 0
82
- for offset in -radius...radius {
83
- let sample = min(max(inner + offset, 0), innerLimit - 1)
84
- let (x, y) = horizontal ? (sample, outer) : (outer, sample)
85
- let pixel = source[x, y]
86
- r += Int(pixel.r); g += Int(pixel.g); b += Int(pixel.b); a += Int(pixel.a)
87
- count += 1
70
+ let kernel = UInt32(max(3, (radius * 2 + 1) | 1))
71
+ buffer.mutateBytes { ptr, count, width, height in
72
+ var src = vImage_Buffer(data: ptr, height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: width * 4)
73
+ var temp = [UInt8](repeating: 0, count: count)
74
+ temp.withUnsafeMutableBufferPointer { tempBuf in
75
+ var dest = vImage_Buffer(data: tempBuf.baseAddress, height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: width * 4)
76
+ for _ in 0..<3 {
77
+ vImageBoxConvolve_ARGB8888(&src, &dest, nil, 0, 0, kernel, kernel, nil, vImage_Flags(kvImageEdgeExtend))
78
+ memcpy(src.data, dest.data, count)
88
79
  }
89
- let (ox, oy) = horizontal ? (inner, outer) : (outer, inner)
90
- buffer[ox, oy] = (UInt8(r / count), UInt8(g / count), UInt8(b / count), UInt8(a / count))
91
80
  }
92
81
  }
93
82
  }
@@ -112,17 +101,25 @@ enum PhotoAdjustmentRenderer {
112
101
 
113
102
  private static func applyPreset(_ image: UIImage, preset: String, strength: Double) -> UIImage {
114
103
  guard let recipe = PhotoFilterPresets.recipe(for: preset), var buffer = PixelBuffer(image: image) else { return image }
115
- for y in 0..<buffer.height {
116
- for x in 0..<buffer.width {
117
- let pixel = buffer[x, y]
118
- let luminance = 0.2126 * Double(pixel.r) + 0.7152 * Double(pixel.g) + 0.0722 * Double(pixel.b)
119
- var r = luminance + (Double(pixel.r) - luminance) * recipe.saturation
120
- var g = luminance + (Double(pixel.g) - luminance) * recipe.saturation
121
- var b = luminance + (Double(pixel.b) - luminance) * recipe.saturation
122
- r = r * recipe.scale + recipe.redOffset
123
- g = g * recipe.scale + recipe.greenOffset
124
- b = b * recipe.scale + recipe.blueOffset
125
- buffer[x, y] = (clampByte(r), clampByte(g), clampByte(b), pixel.a)
104
+ let sat = Float(recipe.saturation)
105
+ let scale = Float(recipe.scale)
106
+ let rOff = Float(recipe.redOffset)
107
+ let gOff = Float(recipe.greenOffset)
108
+ let bOff = Float(recipe.blueOffset)
109
+ buffer.mutateBytes { ptr, _, width, height in
110
+ let totalPixels = width * height
111
+ for i in 0..<totalPixels {
112
+ let offset = i * 4
113
+ let rVal = Float(ptr[offset])
114
+ let gVal = Float(ptr[offset + 1])
115
+ let bVal = Float(ptr[offset + 2])
116
+ let luminance = 0.2126 * rVal + 0.7152 * gVal + 0.0722 * bVal
117
+ let r = (luminance + (rVal - luminance) * sat) * scale + rOff
118
+ let g = (luminance + (gVal - luminance) * sat) * scale + gOff
119
+ let b = (luminance + (bVal - luminance) * sat) * scale + bOff
120
+ ptr[offset] = UInt8(min(max(r, 0), 255))
121
+ ptr[offset + 1] = UInt8(min(max(g, 0), 255))
122
+ ptr[offset + 2] = UInt8(min(max(b, 0), 255))
126
123
  }
127
124
  }
128
125
  guard let filtered = buffer.makeImage() else { return image }
@@ -134,8 +131,5 @@ enum PhotoAdjustmentRenderer {
134
131
  filtered.draw(in: CGRect(origin: .zero, size: image.size), blendMode: .normal, alpha: CGFloat(strength))
135
132
  }
136
133
  }
137
-
138
- private static func clampByte(_ value: Double) -> UInt8 {
139
- UInt8(min(max(value.rounded(), 0), 255))
140
- }
141
134
  }
135
+
@@ -1,18 +1,10 @@
1
1
  import CoreGraphics
2
2
  import UIKit
3
3
 
4
- /// Raw RGBA8 pixel access for a `UIImage`, used by adjustment/effect passes
5
- /// that need direct per-pixel math (tone curve, sharpen, box blur).
6
- ///
7
- /// Important: a bitmap `CGContext` created via `CGContext(data:...)` has its
8
- /// origin at the bottom-left with Y increasing upward (Quartz-native), which
9
- /// is the OPPOSITE of the top-down row order `CGImage`/`UIImage` assume.
10
- /// Drawing into it without first flipping the CTM produces a vertically
11
- /// flipped image once the raw bytes are wrapped back into a `CGImage`. Both
12
- /// `init` and `makeImage()` apply that flip so round-tripping through this
13
- /// buffer preserves the image's orientation.
4
+ /// Raw RGBA8 pixel access for a `UIImage`, optimized for high-throughput
5
+ /// contiguous memory access.
14
6
  struct PixelBuffer {
15
- private(set) var data: [UInt8]
7
+ var data: [UInt8]
16
8
  let width: Int
17
9
  let height: Int
18
10
  private static let bytesPerPixel = 4
@@ -32,23 +24,36 @@ struct PixelBuffer {
32
24
  space: CGColorSpaceCreateDeviceRGB(),
33
25
  bitmapInfo: Self.bitmapInfo
34
26
  ) else { return nil }
35
- context.translateBy(x: 0, y: CGFloat(height))
36
- context.scaleBy(x: 1, y: -1)
37
27
  context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))
38
28
  data = buffer
39
29
  }
40
30
 
31
+ mutating func mutateBytes(_ body: (UnsafeMutablePointer<UInt8>, Int, Int, Int) -> Void) {
32
+ let count = data.count
33
+ let w = width
34
+ let h = height
35
+ data.withUnsafeMutableBufferPointer { buffer in
36
+ if let base = buffer.baseAddress {
37
+ body(base, count, w, h)
38
+ }
39
+ }
40
+ }
41
+
41
42
  func makeImage() -> UIImage? {
42
- var buffer = data
43
- guard let context = CGContext(
44
- data: &buffer,
43
+ guard let provider = CGDataProvider(data: Data(data) as CFData) else { return nil }
44
+ guard let cgImage = CGImage(
45
45
  width: width,
46
46
  height: height,
47
47
  bitsPerComponent: 8,
48
+ bitsPerPixel: 32,
48
49
  bytesPerRow: width * Self.bytesPerPixel,
49
50
  space: CGColorSpaceCreateDeviceRGB(),
50
- bitmapInfo: Self.bitmapInfo
51
- ), let cgImage = context.makeImage() else { return nil }
51
+ bitmapInfo: CGBitmapInfo(rawValue: Self.bitmapInfo),
52
+ provider: provider,
53
+ decode: nil,
54
+ shouldInterpolate: false,
55
+ intent: .defaultIntent
56
+ ) else { return nil }
52
57
  return UIImage(cgImage: cgImage)
53
58
  }
54
59
 
@@ -66,3 +71,4 @@ struct PixelBuffer {
66
71
  }
67
72
  }
68
73
  }
74
+
@@ -20,14 +20,14 @@ final class FilterThumbnailLoader {
20
20
  deliver(cached)
21
21
  return
22
22
  }
23
- // Scale once and reuse for every preset rather than per thumbnail.
24
- let square = squareSource(from: source, size: size)
25
23
  queue.async { [weak self] in
24
+ guard let self else { return }
25
+ let square = self.squareSource(from: source, size: size)
26
26
  let rendered = PhotoAdjustmentRenderer.apply(
27
27
  square,
28
28
  adjustments: PhotoAdjustments(filterPreset: presetID, filterStrength: 100)
29
29
  )
30
- self?.cache.setObject(rendered, forKey: presetID as NSString)
30
+ self.cache.setObject(rendered, forKey: presetID as NSString)
31
31
  DispatchQueue.main.async { deliver(rendered) }
32
32
  }
33
33
  }
@@ -1,10 +1,41 @@
1
1
  #import <PhotoVideoEditorSpec/PhotoVideoEditorSpec.h>
2
2
  #import "PhotoVideoEditor-Swift.h"
3
3
 
4
- @interface PhotoVideoEditor : PhotoVideoEditorSwift <NativePhotoVideoEditorSpec>
4
+ // Objective-C cannot subclass a Swift class, so the TurboModule holds a
5
+ // `PhotoVideoEditorSwift` instance and forwards the spec methods to it.
6
+ @interface PhotoVideoEditor : NSObject <NativePhotoVideoEditorSpec>
5
7
  @end
6
8
 
7
- @implementation PhotoVideoEditor
9
+ @implementation PhotoVideoEditor {
10
+ PhotoVideoEditorSwift *_impl;
11
+ }
12
+
13
+ - (instancetype)init
14
+ {
15
+ if (self = [super init]) {
16
+ _impl = [PhotoVideoEditorSwift new];
17
+ }
18
+ return self;
19
+ }
20
+
21
+ - (void)openEditor:(NSString *)request
22
+ resolve:(RCTPromiseResolveBlock)resolve
23
+ reject:(RCTPromiseRejectBlock)reject
24
+ {
25
+ [_impl openEditor:request resolve:resolve reject:reject];
26
+ }
27
+
28
+ - (void)cancelExport:(NSString *)jobId
29
+ resolve:(RCTPromiseResolveBlock)resolve
30
+ reject:(RCTPromiseRejectBlock)reject
31
+ {
32
+ [_impl cancelExport:jobId resolve:resolve reject:reject];
33
+ }
34
+
35
+ - (NSNumber *)isAvailable
36
+ {
37
+ return [_impl isAvailable];
38
+ }
8
39
 
9
40
  - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
10
41
  (const facebook::react::ObjCTurboModule::InitParams &)params
@@ -30,28 +30,32 @@ public class PhotoVideoEditorSwift: NSObject {
30
30
  features: payload["features"] as? [String: Any] ?? [:],
31
31
  theme: payload["theme"] as? [String: Any] ?? [:],
32
32
  exportOptions: payload["export"] as? [String: Any] ?? [:],
33
- stickerAssets: payload["stickerAssets"] as? [[String: Any]] ?? []
33
+ stickerAssets: payload["stickerAssets"] as? [[String: Any]] ?? [],
34
+ initialStickerId: payload["initialStickerId"] as? String
34
35
  )
35
36
  controller.completion = { [weak self, weak controller] outcome in
36
37
  guard let self else { return }
37
38
  self.editorOpen = false
38
- controller?.dismiss(animated: true)
39
- switch outcome {
40
- case .cancelled:
41
- var result: [String: Any] = ["uri": uri, "type": type, "cancelled": true]
42
- if let mimeType = source["mimeType"] as? String, !mimeType.isEmpty { result["mimeType"] = mimeType }
43
- PhotoVideoEditorSwift.resolveResult(result, resolve: resolve, reject: reject)
44
- case let .success(resultUri, mimeType, width, height, fileSize, durationMs):
45
- var result: [String: Any] = ["uri": resultUri, "type": type, "cancelled": false]
46
- let resolvedMimeType = mimeType ?? (source["mimeType"] as? String)
47
- if let resolvedMimeType, !resolvedMimeType.isEmpty { result["mimeType"] = resolvedMimeType }
48
- if let width { result["width"] = width }
49
- if let height { result["height"] = height }
50
- if let fileSize { result["fileSize"] = fileSize }
51
- if let durationMs { result["duration"] = durationMs }
52
- PhotoVideoEditorSwift.resolveResult(result, resolve: resolve, reject: reject)
53
- case let .failure(code, message):
54
- reject(code, message, nil)
39
+ DispatchQueue.main.async {
40
+ controller?.dismiss(animated: true) {
41
+ switch outcome {
42
+ case .cancelled:
43
+ var result: [String: Any] = ["uri": uri, "type": type, "cancelled": true]
44
+ if let mimeType = source["mimeType"] as? String, !mimeType.isEmpty { result["mimeType"] = mimeType }
45
+ PhotoVideoEditorSwift.resolveResult(result, resolve: resolve, reject: reject)
46
+ case let .success(resultUri, mimeType, width, height, fileSize, durationMs):
47
+ var result: [String: Any] = ["uri": resultUri, "type": type, "cancelled": false]
48
+ let resolvedMimeType = mimeType ?? (source["mimeType"] as? String)
49
+ if let resolvedMimeType, !resolvedMimeType.isEmpty { result["mimeType"] = resolvedMimeType }
50
+ if let width { result["width"] = width }
51
+ if let height { result["height"] = height }
52
+ if let fileSize { result["fileSize"] = fileSize }
53
+ if let durationMs { result["duration"] = durationMs }
54
+ PhotoVideoEditorSwift.resolveResult(result, resolve: resolve, reject: reject)
55
+ case let .failure(code, message):
56
+ reject(code, message, nil)
57
+ }
58
+ }
55
59
  }
56
60
  }
57
61
  DispatchQueue.main.async { presenter.present(controller, animated: true) }
@@ -236,7 +236,7 @@ final class OnlineStickerSheetViewController: UIViewController {
236
236
  }
237
237
  }
238
238
 
239
- private func loadRuntimeSticker(_ sticker: RuntimeSticker, completion: @escaping (URL?, UIImage?) -> Void) {
239
+ func loadRuntimeSticker(_ sticker: RuntimeSticker, completion: @escaping (URL?, UIImage?) -> Void) {
240
240
  if !sticker.uri.lowercased().hasPrefix("https://") {
241
241
  guard let path = SourceResolver.resolvePath(sourceUri: sticker.uri, tempPrefix: "pve_runtime_sticker") else {
242
242
  completion(nil, nil)
@@ -24,19 +24,33 @@ final class VideoEditSession {
24
24
  /// Fires once the source is probed and the initial single-clip timeline is ready.
25
25
  var onClipsReady: (() -> Void)?
26
26
 
27
+ private(set) var naturalSize: CGSize = .zero
28
+ private var cachedAsset: AVURLAsset?
27
29
  private var statusObservation: NSKeyValueObservation?
30
+ private let sourceUri: String
28
31
 
29
32
  init(sourceUri: String) {
30
- let url = URL(fileURLWithPath: SourceResolver.resolvePath(sourceUri: sourceUri, tempPrefix: "pve_video_preview") ?? sourceUri)
33
+ self.sourceUri = sourceUri
34
+ let resolvedPath = SourceResolver.resolvePath(sourceUri: sourceUri, tempPrefix: "pve_video_preview")
35
+ let url = resolvedPath.map { URL(fileURLWithPath: $0) }
36
+ ?? (URL(string: sourceUri)?.scheme != nil ? URL(string: sourceUri)! : URL(fileURLWithPath: sourceUri))
31
37
  let asset = AVURLAsset(url: url)
38
+ cachedAsset = asset
32
39
  let probeItem = AVPlayerItem(asset: asset)
33
- statusObservation = probeItem.observe(\.status, options: [.new]) { [weak self] observedItem, _ in
34
- guard let self, observedItem.status == .readyToPlay, self.clips.isEmpty else { return }
35
- let durationSeconds = CMTimeGetSeconds(asset.duration)
36
- guard durationSeconds.isFinite, durationSeconds > 0 else { return }
37
- self.clips = [VideoClip(sourceUri: sourceUri, originalDurationMs: Int64(durationSeconds * 1000))]
38
- self.rebuildPlayerComposition()
39
- self.onClipsReady?()
40
+ statusObservation = probeItem.observe(\.status, options: [.initial, .new]) { [weak self] observedItem, _ in
41
+ guard let self, observedItem.status == .readyToPlay else { return }
42
+ DispatchQueue.main.async { [weak self] in
43
+ guard let self, self.clips.isEmpty else { return }
44
+ if let track = asset.tracks(withMediaType: .video).first {
45
+ let size = track.naturalSize.applying(track.preferredTransform)
46
+ self.naturalSize = CGSize(width: max(1, abs(size.width)), height: max(1, abs(size.height)))
47
+ }
48
+ let durationSeconds = CMTimeGetSeconds(asset.duration)
49
+ guard durationSeconds.isFinite, durationSeconds > 0 else { return }
50
+ self.clips = [VideoClip(sourceUri: sourceUri, originalDurationMs: Int64(durationSeconds * 1000))]
51
+ self.rebuildPlayerComposition()
52
+ self.onClipsReady?()
53
+ }
40
54
  }
41
55
  player.replaceCurrentItem(with: probeItem)
42
56
  }
@@ -59,8 +73,13 @@ final class VideoEditSession {
59
73
 
60
74
  /// The `AVURLAsset` for one clip's own source file (all clips share one source this milestone).
61
75
  func asset(for clip: VideoClip) -> AVURLAsset {
62
- let url = URL(fileURLWithPath: SourceResolver.resolvePath(sourceUri: clip.sourceUri, tempPrefix: "pve_video_preview") ?? clip.sourceUri)
63
- return AVURLAsset(url: url)
76
+ if let cached = cachedAsset, clip.sourceUri == sourceUri { return cached }
77
+ let resolvedPath = SourceResolver.resolvePath(sourceUri: clip.sourceUri, tempPrefix: "pve_video_preview")
78
+ let url = resolvedPath.map { URL(fileURLWithPath: $0) }
79
+ ?? (URL(string: clip.sourceUri)?.scheme != nil ? URL(string: clip.sourceUri)! : URL(fileURLWithPath: clip.sourceUri))
80
+ let asset = AVURLAsset(url: url)
81
+ if clip.sourceUri == sourceUri { cachedAsset = asset }
82
+ return asset
64
83
  }
65
84
 
66
85
  private func rebuildPlayerComposition() {
@@ -95,5 +114,5 @@ final class VideoEditSession {
95
114
  }
96
115
 
97
116
  private extension Int64 {
98
- func clamped(to range: ClosedRange<Int64>) -> Int64 { min(max(self, range.lowerBound), range.upperBound) }
117
+ func clamped(to range: ClosedRange<Int64>) -> Int64 { Swift.min(Swift.max(self, range.lowerBound), range.upperBound) }
99
118
  }
@@ -39,6 +39,11 @@ function normalizeOptions(input) {
39
39
  positive(output.frameRate, 'export.frameRate');
40
40
  if (input.source.type === 'photo' && output.videoFormat !== undefined) invalid('videoFormat is only valid for video sources.');
41
41
  if (input.source.type === 'video' && output.imageFormat !== undefined) invalid('imageFormat is only valid for photo sources.');
42
+ if (input.initialStickerId !== undefined) {
43
+ if (typeof input.initialStickerId !== 'string' || !input.initialStickerId.trim()) invalid('initialStickerId must be a non-empty string.');
44
+ const matches = Array.isArray(input.stickerAssets) ? input.stickerAssets.filter(asset => asset?.id === input.initialStickerId) : [];
45
+ if (matches.length !== 1 || typeof matches[0]?.uri !== 'string' || !matches[0].uri.trim()) invalid('initialStickerId must match exactly one stickerAssets entry with a non-empty uri.');
46
+ }
42
47
  const features = {
43
48
  ...defaultFeatures,
44
49
  ...input.features
@@ -1 +1 @@
1
- {"version":3,"names":["NativePhotoVideoEditor","PhotoVideoEditorError","normalizeEditorError","addEditorEventListener","addExportProgressListener","EDITOR_PROJECT_SCHEMA_VERSION","mediaTypes","Set","qualities","imageFormats","videoFormats","defaultFeatures","crop","rotate","trim","filters","text","stickers","draw","overlays","invalid","message","positive","value","name","undefined","Number","isFinite","normalizeOptions","input","source","uri","has","type","output","export","quality","imageFormat","videoFormat","maxWidth","maxHeight","videoBitrate","frameRate","features","preserveMetadata","saveToGallery","openEditor","options","JSON","parse","stringify","error","openPhotoEditor","openVideoEditor","cancelExport","jobId","isAvailable"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["NativePhotoVideoEditor","PhotoVideoEditorError","normalizeEditorError","addEditorEventListener","addExportProgressListener","EDITOR_PROJECT_SCHEMA_VERSION","mediaTypes","Set","qualities","imageFormats","videoFormats","defaultFeatures","crop","rotate","trim","filters","text","stickers","draw","overlays","invalid","message","positive","value","name","undefined","Number","isFinite","normalizeOptions","input","source","uri","has","type","output","export","quality","imageFormat","videoFormat","maxWidth","maxHeight","videoBitrate","frameRate","initialStickerId","matches","Array","isArray","stickerAssets","filter","asset","id","length","features","preserveMetadata","saveToGallery","openEditor","options","JSON","parse","stringify","error","openPhotoEditor","openVideoEditor","cancelExport","jobId","isAvailable"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":"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","ignoreList":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EAIZ,kBAAkB,EAElB,kBAAkB,EACnB,MAAM,YAAS,CAAC;AAEjB,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,aAAU,CAAC;AAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAU,CAAC;AACjD,mBAAmB,YAAS,CAAC;AAC7B,OAAO,EAAE,6BAA6B,EAAE,MAAM,cAAW,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,MAAM,cAAW,CAAC;AAsE/C,mEAAmE;AACnE,wBAAsB,UAAU,CAC9B,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,CAAC,CAUvB;AACD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAEvB;AACD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAEvB;AACD,0FAA0F;AAC1F,wBAAsB,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAMnE;AACD,4EAA4E;AAC5E,wBAAgB,WAAW,IAAI,OAAO,CAErC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,aAAa,EACb,YAAY,EAIZ,kBAAkB,EAElB,kBAAkB,EACnB,MAAM,YAAS,CAAC;AAEjB,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,aAAU,CAAC;AAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAU,CAAC;AACjD,mBAAmB,YAAS,CAAC;AAC7B,OAAO,EAAE,6BAA6B,EAAE,MAAM,cAAW,CAAC;AAC1D,YAAY,EAAE,aAAa,EAAE,MAAM,cAAW,CAAC;AA0F/C,mEAAmE;AACnE,wBAAsB,UAAU,CAC9B,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,CAAC,CAUvB;AACD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAEvB;AACD,wBAAgB,eAAe,CAC7B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAEvB;AACD,0FAA0F;AAC1F,wBAAsB,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAMnE;AACD,4EAA4E;AAC5E,wBAAgB,WAAW,IAAI,OAAO,CAErC"}
@@ -53,7 +53,7 @@ export interface FontAsset {
53
53
  /** A consumer-provided sticker image, selectable from the Stickers tool. */
54
54
  export interface StickerAsset {
55
55
  /** Stable identifier, echoed back on layers created from this asset. */ id: string;
56
- /** Sticker image URI. Supports HTTPS URLs and local device file/content URIs. */ uri: string;
56
+ /** Sticker image URI. Supports HTTPS URLs, local device file/content URIs, and data:image/png;base64 URIs. */ uri: string;
57
57
  }
58
58
  /** Options accepted by {@link openEditor}. */
59
59
  export interface EditorOptions {
@@ -62,6 +62,7 @@ export interface EditorOptions {
62
62
  /** Native UI theme. */ theme?: EditorTheme;
63
63
  /** Export preferences. */ export?: ExportOptions;
64
64
  /** Requests saving to the gallery. Currently ignored by the placeholder. */ saveToGallery?: boolean;
65
+ /** ID in stickerAssets to insert at the media center when the editor opens. */ initialStickerId?: string;
65
66
  /** Consumer-provided stickers offered by the Stickers tool, in addition to the small built-in set and end-user uploads. */ stickerAssets?: StickerAsset[];
66
67
  /** Consumer-provided fonts selectable from the Text tool. */ fonts?: FontAsset[];
67
68
  }