Releases: SteliyanH/kadr-photos
Release list
v0.7.0 — iOS 17 platform floor
Mechanical platform-floor bump to iOS 17 / macOS 14 / visionOS 1 (tvOS still excluded) so KadrPhotos can depend on Kadr ≥ 0.15.0. Part of the coordinated ecosystem iOS 17 move. Dropped 8 redundant @available annotations; refreshed stale docstrings referencing Kadr's removed speed(_:)/speed(curve:) overloads (docs only — no code called them). No behavior change. Consumers needing the iOS 16 floor stay on 0.6.x. 80 tests pass.
Full diff: v0.6.0...v0.7.0
v0.6.0 — HDR-aware resolution + iOS 17 picker async + Live Photos depth
Reopened cycle — three resolution-side surfaces driven by downstream consumers. Pure additive; every v0.5 call site compiles unchanged. Kadr floor stays at ≥ 0.9.2.
Added
PhotosClipResolver.videoHDRMetadata(of:)— async; reads transfer function + color primaries + color matrix and detects Dolby Vision via codec FourCC, without pulling the full media.VideoHDRMetadatavalue type carries the result. Pairs with kadr-pro for the export side; the resolution side stays free so consumers can detect-and-route ("this is HDR" without "this exports HDR").PhotoPicker(configuration:iOS17AsyncResults:)— iOS 17 / macOS 14 / visionOS 1 initializer that delivers picked items as anAsyncStream<PhotoPickerResult>; caller iterates withfor await. The iOS 16 closure / binding path stays for the deployment floor.PhotoPickernow stores aDeliveryenum (binding vs. asyncStream) and dispatches throughPhotoPicker.deliver(_:via:).PhotosClipResolver.depthMap(from:)— async; returnsCVPixelBuffer?for iPhone 12+ Live Photos carrying disparity / depth / portrait-effects-matte auxiliary data. Detection-only; aux-type preference disparity → depth → matte. Vision-side cutout / segmentation stays in kadr-pro.
Tests
24 new tests across VideoHDRMetadataTests (12), PhotoPickerAsyncResultsTests (4), DepthExtractionTests (8). Suite total: 80.
Notes
- The "iOS 17 PHPicker.results AsyncSequence" framing in the planning doc was aspirational —
PHPickerViewControllerstill uses its delegate. The new overload wraps the delegate in anAsyncStreamso callers getfor awaitergonomics; behavior matches a single batched delivery on dismiss. - Depth extraction is best-effort: any failure path (auth, iCloud, decode) collapses to
nil. Callers branch on presence; no thrown errors. Free-vs-premium line stays clean — we expose the depth buffer, kadr-pro uses it. - HDR detection covers HDR10 / HLG transfer functions plus Dolby Vision profiles via codec FourCC. Color-space conversion is explicitly out of scope (kadr-pro).
Full changelog: CHANGELOG.md.
v0.5.0 — Slow-motion + album asset listing
KadrPhotos v0.5.0. Pure additive — Kadr floor stays at 0.9.2.
Highlights
PhotosClipResolver.slowMotion(asset:options:progress:)— async resolver for high-frame-rate slow-motion videos. Always overridesvideoExportPresettoAVAssetExportPresetPassthroughso the original 60 / 120 / 240 fps source survives the export. Consumers chainclip.speed(0.25)for the classic 8× slo-mo at 30 fps playback.PhotosClipResolver.videoFrameRate(of:)+slowMotionSpeed(originalFrameRate:playbackFrameRate:)— async frame-rate read + pure speed-multiplier helper for choosing the playback ramp programmatically.PhotosClipError.notSlowMotion— new error case thrown by both slow-mo entry points for assets withoutvideoHighFrameRatesubtype.PhotosClipResolver.assets(in: PHAssetCollection, mediaType:)+smartAlbum(_:)— programmatic album listing. Lets editor apps open on the user's Slo-mo, Favorites, or any other smart album without scoping the picker UI (Apple'sPHPickerConfigurationdoesn't expose album scoping in any current SDK).
Compatibility
KadrPhotos 0.5.0 requires Kadr ≥ 0.9.2 (unchanged from v0.1).
11 new tests across the cycle. Cycle considered feature-complete pending kadr v1.0.
See CHANGELOG for the full entry.
v0.4.0 — Metadata + overlay helpers
Closes the v0.x cycle. Surfaces PHAsset metadata as a kadr-side value type and adds thin helpers that bridge a PHAsset directly to kadr's overlay types.
What's new
import Kadr
import KadrPhotos
// Metadata snapshot
let meta = PhotosClipResolver.metadata(of: asset)
print(meta.creationDate, meta.location, meta.subtypes.contains(.livePhoto))
// Logo from Photos as a static ImageOverlay
let logo = try await PhotosClipResolver.imageOverlay(
asset: logoAsset,
position: .topRight,
size: .normalized(width: 0.15, height: 0.15),
anchor: .topRight
)
// Sticker with rotation + shadow
let sticker = try await PhotosClipResolver.stickerOverlay(
asset: stickerAsset,
position: .bottomLeft,
rotation: -0.15,
shadow: .init(color: .black, radius: 8, offset: .init(width: 4, height: 4), opacity: 0.6)
)
let video = Video {
VideoClip(url: footage)
}
.overlay(logo)
.overlay(sticker)- `PhotoAssetMetadata` value type — `creationDate`, `modificationDate`, `location` (`CLLocation`), `pixelSize`, `videoDuration`, `subtypes`, `isFavorite`, `burstIdentifier`, `mediaKind`. `Equatable` + `@unchecked Sendable`.
- `PhotoAssetSubtypes` OptionSet — kadr-side mirror of `PHAssetMediaSubtype` (`.livePhoto` / `.panorama` / `.hdr` / `.screenshot` / `.depthEffect` / etc.). `PhotoAssetSubtypes.from(_:)` exposes the bridge.
- `PhotosClipResolver.metadata(of:)` — synchronous PHAsset property read; no iCloud round-trip.
- `PhotosClipResolver.imageOverlay(asset:)` — PHAsset → `Kadr.ImageOverlay`.
- `PhotosClipResolver.stickerOverlay(asset:)` — PHAsset → `Kadr.StickerOverlay` (adds rotation + shadow).
Compatibility
- Requires kadr ≥ 0.9.2 (unchanged).
- iOS 16+ / macOS 13+ / visionOS 1+. tvOS still excluded.
- Pure additive — every v0.3 call site compiles unchanged.
Tests
Suite: 36 → 57 (+21).
v0.x cycle complete
KadrPhotos is now feature-complete on the originally-scoped surface:
- v0.1.0 — video + image PHAsset resolution
- v0.2.0 — Live Photo
- v0.3.0 — `PhotoPicker` SwiftUI wrapper
- v0.4.0 — metadata + overlay helpers
Full notes in CHANGELOG.md.
v0.3.0 — PhotoPicker SwiftUI wrapper
The PhotosUI bridge. Cross-platform SwiftUI wrapper around `PHPickerViewController` that returns directly into kadr clip types — bypasses the manual `PHAsset` round-trip for the common "user picks N items, build a video" flow.
What's new
import SwiftUI
import Kadr
import KadrPhotos
struct ContentView: View {
@State private var picked: [PhotoPickerResult] = []
@State private var showPicker = false
@State private var clips: [any Clip] = []
var body: some View {
Button(\"Pick from Photos\") { showPicker = true }
.sheet(isPresented: \$showPicker) {
PhotoPicker(
selection: \$picked,
configuration: .init(selectionLimit: 5, filter: .any)
)
}
.task(id: picked.map(\\.assetIdentifier)) {
clips = (try? await PhotosClipResolver.clips(from: picked)) ?? []
}
}
}- `PhotoPicker(selection:configuration:)` — UIKit on iOS / visionOS, AppKit on macOS via `UIViewControllerRepresentable` / `NSViewControllerRepresentable`.
- `PhotoPickerResult` — `Sendable` + `Equatable` + `Identifiable`. Wraps `assetIdentifier`; `@MainActor resolveAsset()` returns the `PHAsset`.
- `PhotoPicker.Configuration` — selectionLimit, filter, preferredAssetRepresentationMode. Sensible defaults.
- `PhotoPicker.Filter` — `.images` / `.videos` / `.livePhotos` / `.any`.
- `PhotoPicker.AssetRepresentationMode` — `.automatic` / `.current` / `.compatible`.
- `PhotosClipResolver.clip(from:)` + `clips(from:)` — convenience that dispatches on `PHAsset.mediaType` and returns `any Clip`. `CMTime` and `TimeInterval` overloads.
Compatibility
- Requires kadr ≥ 0.9.2 (unchanged).
- iOS 16+ / macOS 13+ / visionOS 1+. tvOS still excluded.
- Pure additive — every v0.2 call site compiles unchanged.
- The picker doesn't require library authorization to display — but `resolveAsset()` and `clip(from:)` do (same v0.1 contract).
Tests
Suite: 21 → 36 (+15).
What's next
- v0.4.0 — PHAsset metadata exposure (creation date / location / EXIF) + direct asset → `ImageOverlay` / `StickerOverlay` helpers
Full notes in CHANGELOG.md.
v0.2.0 — Live Photo
Live Photo support. Surfaces both halves of a Live Photo PHAsset as kadr clip types — drop the motion as a `VideoClip` (animated story card / boomerang), drop the still as an `ImageClip`, or use both.
What's new
import Kadr
import KadrPhotos
// Motion half — animated story card
let motion = try await PhotosClipResolver.livePhotoMotion(asset: livePhotoAsset)
let video = Video {
motion
motion.reversed() // boomerang
}
// Still half — explicit Live-Photo guard
let still = try await PhotosClipResolver.livePhotoStill(asset: livePhotoAsset, duration: 3.0)- `PhotosClipResolver.livePhotoMotion(asset:progress:)` — extracts the paired-video resource via `PHAssetResource` + `PHAssetResourceManager.writeData` to a temp `.mov` URL.
- `PhotosClipResolver.livePhotoStill(asset:duration:options:progress:)` — symmetric Live-Photo-guarded wrapper around `image()`. `CMTime` and `TimeInterval` overloads.
- New `PhotosClipError.notALivePhoto` for assets where `mediaSubtypes.contains(.photoLive)` is false.
Compatibility
- Requires kadr ≥ 0.9.2 (unchanged).
- iOS 16+ / macOS 13+ / visionOS 1+. tvOS still excluded.
- Pure additive — every v0.1 call site compiles unchanged.
Tests
Suite: 15 → 21 (+6).
What's next
- v0.3.0 — `PHPickerViewController` SwiftUI wrapper
- v0.4.0 — PHAsset metadata exposure + direct asset → `ImageOverlay` / `StickerOverlay` helpers
Full notes in CHANGELOG.md.
v0.1.0 — Video + image PHAsset resolution
First release of kadr-photos — Photos library integration for kadr.
What's new
import Kadr
import KadrPhotos
import Photos
let videoClip = try await PhotosClipResolver.video(asset: phVideoAsset) { progress in
iCloudProgressBar.fractionCompleted = progress
}
let imageClip = try await PhotosClipResolver.image(asset: phImageAsset, duration: 3.0)
let video = Video {
videoClip
imageClip
}- `PhotosClipResolver.video(asset:options:progress:)` — async, returns `VideoClip`. Downloads from iCloud if needed, then re-encodes via `AVAssetExportSession` to a temp `.mp4`.
- `PhotosClipResolver.image(asset:duration:options:progress:)` — async, returns `ImageClip` at the requested target size. `CMTime` and `TimeInterval` overloads.
- `Options` — image target size, content mode, delivery mode, video export preset. Sensible defaults.
- `PhotosClipError` — typed errors: `unauthorized`, `wrongMediaType`, `missingMedia`, `iCloudDownload`, `videoExportFailed`.
- iCloud download progress via `@Sendable (Double) -> Void` callback.
- `ImageContentMode` / `ImageDeliveryMode` re-exported subsets so consumers don't need to import `Photos` for option setting.
Compatibility
- Requires kadr ≥ 0.9.2.
- iOS 16+, macOS 13+, visionOS 1+. tvOS excluded — `Photos.framework` unavailable on tvOS.
- Required entitlement (consumer): `NSPhotoLibraryUsageDescription`.
- No third-party dependencies. Pure Swift + Foundation + AVFoundation + Photos.
Tests
15 unit tests covering pure helpers (mappings, options, target sizes, error equality). PHAsset-backed paths require a real photo library + authorization and are covered by integration testing in consuming apps.
What's next
- v0.2.0 — Live Photo support (still + motion as a unit)
- v0.3.0+ — `PHPickerViewController` SwiftUI wrapper
Full notes in CHANGELOG.md.