Skip to content

Commit ef794be

Browse files
committed
Fixes #33
1 parent c51306c commit ef794be

2 files changed

Lines changed: 100 additions & 14 deletions

File tree

README.md

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ The library provides customizable audio and video encoding options unlike `AVAss
1111

1212
- **🚀 Modern Async/Await API** - Native Swift concurrency support with `async/await` and `AsyncSequence`
1313
- **🌈 HDR Video Support** - Automatic detection and preservation of HLG and HDR10 content with 10-bit HEVC
14+
- **📐 Scaling Mode Fixes** - AVVideoScalingModeKey now works correctly for aspect-fill and resize (#33)
1415
- **⚡ Better Performance** - Proper memory management with autoreleasepool in encoding loop
1516
- **🎯 QoS Configuration** - Control export priority to prevent thread priority inversion (PR #44)
1617
- **🔒 Swift 6 Strict Concurrency** - Full `Sendable` conformance and thread-safety
@@ -242,6 +243,47 @@ exporter.videoOutputConfiguration = [
242243
]
243244
```
244245

246+
#### Video Scaling Modes
247+
248+
Control how videos are scaled to target dimensions using `AVVideoScalingModeKey` ([Fixed in 1.0.1 - Issue #33](https://github.com/NextLevel/NextLevelSessionExporter/issues/33)):
249+
250+
```swift
251+
exporter.videoOutputConfiguration = [
252+
AVVideoCodecKey: AVVideoCodecType.h264,
253+
AVVideoWidthKey: 720,
254+
AVVideoHeightKey: 1280,
255+
AVVideoScalingModeKey: AVVideoScalingModeResizeAspectFill // Choose your scaling mode
256+
]
257+
```
258+
259+
**Available Scaling Modes:**
260+
261+
- **`AVVideoScalingModeResizeAspectFill`** (Recommended)
262+
- Scales video to fill the target dimensions while maintaining aspect ratio
263+
- May crop content to fill the entire frame
264+
- Ideal for converting landscape → portrait or vice versa
265+
266+
- **`AVVideoScalingModeResize`**
267+
- Stretches video to exact target dimensions
268+
- Does not maintain aspect ratio
269+
- Use when you want non-uniform scaling
270+
271+
- **`AVVideoScalingModeResizeAspect`** (Default if not specified)
272+
- Fits entire video within target dimensions while maintaining aspect ratio
273+
- May add letterboxing/pillarboxing (black bars)
274+
- Legacy behavior for backward compatibility
275+
276+
**Example: Landscape → Portrait Conversion**
277+
```swift
278+
// Convert 1920x1080 landscape video to 720x1280 portrait
279+
exporter.videoOutputConfiguration = [
280+
AVVideoCodecKey: AVVideoCodecType.h264,
281+
AVVideoWidthKey: 720,
282+
AVVideoHeightKey: 1280,
283+
AVVideoScalingModeKey: AVVideoScalingModeResizeAspectFill // Crops sides, fills frame
284+
]
285+
```
286+
245287
### Custom Audio Encoding
246288

247289
Fine-tune audio settings for optimal file size and quality:
@@ -599,12 +641,20 @@ let exporter = NextLevelSessionExporter(withAsset: asset)
599641
- Avoid frame-by-frame processing if not needed
600642
- Test on a physical device (simulator performance varies)
601643

602-
### Video Orientation is Wrong
644+
### Video Orientation or Scaling Issues
603645

604646
The library automatically handles video orientation and transforms. If you're experiencing issues:
647+
648+
**Orientation Problems:**
605649
- Let the library create the video composition automatically (don't set `videoComposition`)
606650
- Ensure your video output configuration includes proper width/height
607651

652+
**Scaling Not Working (Fixed in 1.0.1):**
653+
- If video doesn't fill the target dimensions as expected, use `AVVideoScalingModeKey`
654+
- See the [Video Scaling Modes](#video-scaling-modes) section for details
655+
- Common issue: landscape → portrait conversion with black bars
656+
- Solution: Use `AVVideoScalingModeResizeAspectFill`
657+
608658
### Audio Track Missing
609659

610660
**Issue:** Some videos export without audio.

Sources/NextLevelSessionExporter.swift

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -787,23 +787,59 @@ extension NextLevelSessionExporter {
787787
naturalSize.width = naturalSize.height
788788
naturalSize.height = tempWidth
789789
}
790-
videoComposition.renderSize = naturalSize
791790

792-
// center the video
791+
// Check if user specified a scaling mode (Issue #33)
792+
let scalingMode = videoConfiguration[AVVideoScalingModeKey] as? String
793793

794-
var ratio: CGFloat = 0
795-
let xRatio: CGFloat = targetSize.width / naturalSize.width
796-
let yRatio: CGFloat = targetSize.height / naturalSize.height
797-
ratio = min(xRatio, yRatio)
794+
if scalingMode == AVVideoScalingModeResizeAspectFill || scalingMode == AVVideoScalingModeResize {
795+
// User wants aspect-fill or resize - respect their choice
796+
videoComposition.renderSize = targetSize
798797

799-
let postWidth = naturalSize.width * ratio
800-
let postHeight = naturalSize.height * ratio
801-
let transX = (targetSize.width - postWidth) * 0.5
802-
let transY = (targetSize.height - postHeight) * 0.5
798+
// Calculate scale and translation based on scaling mode
799+
let xRatio: CGFloat = targetSize.width / naturalSize.width
800+
let yRatio: CGFloat = targetSize.height / naturalSize.height
803801

804-
var matrix = CGAffineTransform(translationX: (transX / xRatio), y: (transY / yRatio))
805-
matrix = matrix.scaledBy(x: (ratio / xRatio), y: (ratio / yRatio))
806-
transform = transform.concatenating(matrix)
802+
let ratio: CGFloat
803+
if scalingMode == AVVideoScalingModeResizeAspectFill {
804+
// Aspect fill: scale to fill entire target (may crop)
805+
ratio = max(xRatio, yRatio)
806+
} else {
807+
// Resize: stretch to fill target exactly
808+
ratio = 1.0
809+
}
810+
811+
let postWidth = naturalSize.width * ratio
812+
let postHeight = naturalSize.height * ratio
813+
let transX = (targetSize.width - postWidth) * 0.5
814+
let transY = (targetSize.height - postHeight) * 0.5
815+
816+
var matrix = CGAffineTransform(translationX: (transX / xRatio), y: (transY / yRatio))
817+
if scalingMode == AVVideoScalingModeResize {
818+
// Stretch to exact dimensions
819+
matrix = matrix.scaledBy(x: xRatio, y: yRatio)
820+
} else {
821+
// Scale uniformly for aspect fill
822+
matrix = matrix.scaledBy(x: (ratio / xRatio), y: (ratio / yRatio))
823+
}
824+
transform = transform.concatenating(matrix)
825+
} else {
826+
// No scaling mode specified or aspect-fit - use legacy centering logic
827+
videoComposition.renderSize = naturalSize
828+
829+
var ratio: CGFloat = 0
830+
let xRatio: CGFloat = targetSize.width / naturalSize.width
831+
let yRatio: CGFloat = targetSize.height / naturalSize.height
832+
ratio = min(xRatio, yRatio)
833+
834+
let postWidth = naturalSize.width * ratio
835+
let postHeight = naturalSize.height * ratio
836+
let transX = (targetSize.width - postWidth) * 0.5
837+
let transY = (targetSize.height - postHeight) * 0.5
838+
839+
var matrix = CGAffineTransform(translationX: (transX / xRatio), y: (transY / yRatio))
840+
matrix = matrix.scaledBy(x: (ratio / xRatio), y: (ratio / yRatio))
841+
transform = transform.concatenating(matrix)
842+
}
807843

808844
// make the composition
809845

0 commit comments

Comments
 (0)