expo-pro-video-editor 0.2.10 → 0.2.12

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.
@@ -78,7 +78,23 @@ enum PhotoLibraryVideoExporter {
78
78
  let sourceAsset = exportSession.asset
79
79
  if let videoTrack = sourceAsset.tracks(withMediaType: .video).first {
80
80
  let transform = videoTrack.preferredTransform
81
- let isPortrait = abs(transform.b) == 1 && abs(transform.c) == 1
81
+ // An exact `abs(transform.b) == 1` equality check is too brittle for this: a
82
+ // photo-library asset's preferredTransform routinely carries a rotation that
83
+ // is only approximately ±90°/±270° (stabilization/lens-correction adjustments,
84
+ // mirrored front-camera captures, Photos-applied edits even under `.original`),
85
+ // so `transform.b`/`transform.c` land at e.g. 0.9998 rather than exactly 1.0.
86
+ // That made `isPortrait` false for real portrait footage while the layer
87
+ // instruction below still applied the (near-90°) rotation unconditionally —
88
+ // the rotated frame then landed almost entirely outside the declared
89
+ // (unrotated, landscape) renderSize, and AVAssetExportSession "succeeds" with
90
+ // a video track that composites to black. Audio isn't touched by this video
91
+ // composition, so it played fine while the picture stayed dark — exactly the
92
+ // "audio plays, video is black" symptom this exists to fix. Use the same
93
+ // atan2-based angle tolerance already used elsewhere in this package (see
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
82
98
  let naturalSize = videoTrack.naturalSize
83
99
  let renderSize = isPortrait
84
100
  ? CGSize(width: naturalSize.height, height: naturalSize.width)
@@ -91,8 +107,30 @@ enum PhotoLibraryVideoExporter {
91
107
  let instruction = AVMutableVideoCompositionInstruction()
92
108
  instruction.timeRange = CMTimeRange(start: .zero, duration: sourceAsset.duration)
93
109
 
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
+ }
131
+
94
132
  let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
95
- layerInstruction.setTransform(transform, at: .zero)
133
+ layerInstruction.setTransform(finalTransform, at: .zero)
96
134
  instruction.layerInstructions = [layerInstruction]
97
135
  composition.instructions = [instruction]
98
136
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-pro-video-editor",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
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",