expo-pro-video-editor 0.2.12 → 0.2.13

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.
@@ -92,9 +92,8 @@ enum PhotoLibraryVideoExporter {
92
92
  // "audio plays, video is black" symptom this exists to fix. Use the same
93
93
  // atan2-based angle tolerance already used elsewhere in this package (see
94
94
  // VideoTranscoder.calculateOutputSize / VideoSequenceBuilder) instead.
95
- let rotationAngle = atan2(transform.b, transform.a)
96
- let absRotationAngle = abs(rotationAngle)
97
- let isPortrait = absRotationAngle > .pi / 4 && absRotationAngle < 3 * .pi / 4
95
+ let rotationAngle = abs(atan2(transform.b, transform.a))
96
+ let isPortrait = rotationAngle > .pi / 4 && rotationAngle < 3 * .pi / 4
98
97
  let naturalSize = videoTrack.naturalSize
99
98
  let renderSize = isPortrait
100
99
  ? CGSize(width: naturalSize.height, height: naturalSize.width)
@@ -107,27 +106,30 @@ enum PhotoLibraryVideoExporter {
107
106
  let instruction = AVMutableVideoCompositionInstruction()
108
107
  instruction.timeRange = CMTimeRange(start: .zero, duration: sourceAsset.duration)
109
108
 
110
- // `preferredTransform` alone rotates the frame about (0, 0) — for a 90°/270°
111
- // rotated portrait source that lands the rotated bounding box almost entirely
112
- // outside `renderSize`, producing a video track that composites to black while
113
- // audio (untouched by this composition) plays fine. A translation back into the
114
- // render bounds is required after the rotation, same as the working pattern in
115
- // CompositionBuilder.calculateTransform. There's no scaling here (1:1 passthrough
116
- // export), so this only needs the centering translation, with X/Y swapped for the
117
- // 90°/270° case since translatedBy operates in the already-rotated coordinate space.
118
- var finalTransform = transform
119
- let isRotated90Or270 = abs(absRotationAngle - .pi / 2) < 0.01
120
- if isRotated90Or270 {
121
- let translateX = (renderSize.width - naturalSize.height) / 2
122
- let translateY = (renderSize.height - naturalSize.width) / 2
123
- finalTransform = finalTransform.translatedBy(x: translateY, y: translateX)
124
- } else if isPortrait {
125
- // Non-axis-aligned "portrait" angle (stabilization/lens-correction skew) — fall
126
- // back to centering in the unrotated natural-size space.
127
- let translateX = (renderSize.width - naturalSize.width) / 2
128
- let translateY = (renderSize.height - naturalSize.height) / 2
129
- finalTransform = finalTransform.translatedBy(x: translateX, y: translateY)
130
- }
109
+ // `preferredTransform` alone rotates the frame about (0, 0) — the rotated bounding
110
+ // box can land anywhere relative to `renderSize` depending on the *actual* tx/ty
111
+ // baked into this specific asset's transform (not just its rotation angle). This
112
+ // was observed on a real device-recorded portrait asset whose preferredTransform's
113
+ // tx was 1088 — neither the natural width nor height — which put the rotated frame
114
+ // at x ∈ [512, 1088], entirely outside a 576-wide renderSize. A translation derived
115
+ // from naturalSize/renderSize assuming a "clean" rotation (as an earlier version of
116
+ // this fix did) computes zero correction in that case and leaves the frame
117
+ // off-canvas, producing a video track that composites to black while audio
118
+ // (untouched by this composition) plays fine.
119
+ //
120
+ // The robust fix doesn't assume anything about tx/ty: apply the raw transform to
121
+ // the track's actual corners, find the resulting bounding box, and translate that
122
+ // box's origin back to (0, 0) — this corrects the frame into `renderSize` no matter
123
+ // what translation the source transform happens to carry.
124
+ let corners = [
125
+ CGPoint(x: 0, y: 0),
126
+ CGPoint(x: naturalSize.width, y: 0),
127
+ CGPoint(x: 0, y: naturalSize.height),
128
+ CGPoint(x: naturalSize.width, y: naturalSize.height),
129
+ ].map { $0.applying(transform) }
130
+ let minX = corners.map(\.x).min() ?? 0
131
+ let minY = corners.map(\.y).min() ?? 0
132
+ let finalTransform = transform.concatenating(CGAffineTransform(translationX: -minX, y: -minY))
131
133
 
132
134
  let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
133
135
  layerInstruction.setTransform(finalTransform, at: .zero)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-pro-video-editor",
3
- "version": "0.2.12",
3
+ "version": "0.2.13",
4
4
  "description": "Native video composition (trim, filters, text/image overlays, audio mixing) for Expo/React Native, adapted from pro_video_editor's AVFoundation and Media3 Transformer implementation.",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",