Skip to content

Releases: SteliyanH/kadr-photos

v0.7.0 — iOS 17 platform floor

Choose a tag to compare

@SteliyanH SteliyanH released this 02 Jul 18:01

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

Choose a tag to compare

@SteliyanH SteliyanH released this 28 May 06:26

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. VideoHDRMetadata value 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 an AsyncStream<PhotoPickerResult>; caller iterates with for await. The iOS 16 closure / binding path stays for the deployment floor. PhotoPicker now stores a Delivery enum (binding vs. asyncStream) and dispatches through PhotoPicker.deliver(_:via:).
  • PhotosClipResolver.depthMap(from:) — async; returns CVPixelBuffer? 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 — PHPickerViewController still uses its delegate. The new overload wraps the delegate in an AsyncStream so callers get for await ergonomics; 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

Choose a tag to compare

@SteliyanH SteliyanH released this 03 May 16:03

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 overrides videoExportPreset to AVAssetExportPresetPassthrough so the original 60 / 120 / 240 fps source survives the export. Consumers chain clip.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 without videoHighFrameRate subtype.
  • 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's PHPickerConfiguration doesn'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

Choose a tag to compare

@SteliyanH SteliyanH released this 30 Apr 09:38

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

Choose a tag to compare

@SteliyanH SteliyanH released this 30 Apr 08:56

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

Choose a tag to compare

@SteliyanH SteliyanH released this 30 Apr 07:11

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

Choose a tag to compare

@SteliyanH SteliyanH released this 30 Apr 06:28

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.